/Users/alexjokela/projects/lattice/src/eval.c
Line | Count | Source |
1 | | #include "eval.h" |
2 | | #include "lattice.h" |
3 | | #include "string_ops.h" |
4 | | #include "format_ops.h" |
5 | | #include "builtins.h" |
6 | | #include "array_ops.h" |
7 | | #include "net.h" |
8 | | #include "tls.h" |
9 | | #include "json.h" |
10 | | #include "math_ops.h" |
11 | | #include "env_ops.h" |
12 | | #include "time_ops.h" |
13 | | #include "datetime_ops.h" |
14 | | #include "type_ops.h" |
15 | | #include "fs_ops.h" |
16 | | #include "path_ops.h" |
17 | | #include "regex_ops.h" |
18 | | #include "crypto_ops.h" |
19 | | #include "process_ops.h" |
20 | | #include "http.h" |
21 | | #include "toml_ops.h" |
22 | | #include "yaml_ops.h" |
23 | | #include "lexer.h" |
24 | | #include "parser.h" |
25 | | #include "channel.h" |
26 | | #include "ext.h" |
27 | | #include "runtime.h" |
28 | | #include <stdlib.h> |
29 | | #include <stdint.h> |
30 | | #include <string.h> |
31 | | #include <stdio.h> |
32 | | #include <time.h> |
33 | | #include <limits.h> |
34 | | #include <ctype.h> |
35 | | #include <libgen.h> |
36 | | #include <sys/resource.h> |
37 | | #ifndef __EMSCRIPTEN__ |
38 | | #include <pthread.h> |
39 | | #endif |
40 | | |
41 | | /* Monotonic clock in nanoseconds */ |
42 | 15.8k | static uint64_t now_ns(void) { |
43 | 15.8k | struct timespec ts; |
44 | 15.8k | clock_gettime(CLOCK_MONOTONIC, &ts); |
45 | 15.8k | return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec; |
46 | 15.8k | } |
47 | | |
48 | | /* ── Memory stats helpers ── */ |
49 | | |
50 | 868 | static void stats_init(MemoryStats *s) { memset(s, 0, sizeof(*s)); } |
51 | 349 | static void stats_freeze(MemoryStats *s) { s->freezes++; } |
52 | 196 | static void stats_thaw(MemoryStats *s) { s->thaws++; } |
53 | 3 | static void stats_deep_clone(MemoryStats *s) { s->deep_clones++; } |
54 | 3.67k | static void stats_array(MemoryStats *s) { s->array_allocs++; } |
55 | 196 | static void stats_struct(MemoryStats *s) { s->struct_allocs++; } |
56 | 148 | static void stats_closure(MemoryStats *s) { s->closure_allocs++; } |
57 | 4.69k | static void stats_binding(MemoryStats *s) { s->bindings_created++; } |
58 | 1.25k | static void stats_fn_call(MemoryStats *s) { s->fn_calls++; } |
59 | 821 | static void stats_closure_call(MemoryStats *s) { s->closure_calls++; } |
60 | 1 | static void stats_forge(MemoryStats *s) { s->forge_blocks++; } |
61 | | |
62 | 7.35k | static void stats_scope_push(MemoryStats *s) { |
63 | 7.35k | s->scope_pushes++; |
64 | 7.35k | s->current_scope_depth++; |
65 | 7.35k | if (s->current_scope_depth > s->peak_scope_depth) |
66 | 1.53k | s->peak_scope_depth = s->current_scope_depth; |
67 | 7.35k | } |
68 | | |
69 | 7.35k | static void stats_scope_pop(MemoryStats *s) { |
70 | 7.35k | s->scope_pops++; |
71 | 7.35k | if (s->current_scope_depth > 0) s->current_scope_depth--; |
72 | 7.35k | } |
73 | | |
74 | | /* ── Call stack helpers (stack traces) ── */ |
75 | | |
76 | 1.67k | static void ev_push_frame(Evaluator *ev, const char *name) { |
77 | 1.67k | if (ev->call_depth >= ev->call_stack_cap) { |
78 | 839 | ev->call_stack_cap = ev->call_stack_cap ? ev->call_stack_cap * 2 : 16; |
79 | 839 | ev->call_stack = realloc(ev->call_stack, ev->call_stack_cap * sizeof(const char *)); |
80 | 839 | } |
81 | 1.67k | ev->call_stack[ev->call_depth++] = name; |
82 | 1.67k | } |
83 | | |
84 | 1.57k | static void ev_pop_frame(Evaluator *ev) { |
85 | 1.57k | if (ev->call_depth > 0) ev->call_depth--; |
86 | 1.57k | } |
87 | | |
88 | | /* Format a stack trace and append it to an error message */ |
89 | 96 | static char *ev_attach_trace(Evaluator *ev, char *msg) { |
90 | 96 | if (ev->call_depth == 0) return msg; |
91 | 92 | size_t msg_len = strlen(msg); |
92 | | /* Estimate space: header + per-frame lines */ |
93 | 92 | size_t extra = 32; |
94 | 192 | for (size_t i = 0; i < ev->call_depth; i++) |
95 | 100 | extra += strlen(ev->call_stack[i]) + 16; |
96 | 92 | char *out = realloc(msg, msg_len + extra); |
97 | 92 | if (!out) return msg; |
98 | 92 | size_t pos = msg_len; |
99 | 92 | size_t rem = extra; |
100 | 92 | int n = snprintf(out + pos, rem, "\nstack trace:"); |
101 | 92 | pos += (size_t)n; rem -= (size_t)n; |
102 | 192 | for (size_t i = ev->call_depth; i > 0; i--) { |
103 | 100 | n = snprintf(out + pos, rem, "\n in %s()", ev->call_stack[i - 1]); |
104 | 100 | pos += (size_t)n; rem -= (size_t)n; |
105 | 100 | } |
106 | 92 | return out; |
107 | 92 | } |
108 | | |
109 | | /* ── EvalResult helpers ── */ |
110 | | |
111 | 82.6k | static EvalResult eval_ok(LatValue v) { |
112 | 82.6k | EvalResult r; |
113 | 82.6k | r.ok = true; |
114 | 82.6k | r.value = v; |
115 | 82.6k | r.error = NULL; |
116 | 82.6k | r.cf.tag = CF_NONE; |
117 | 82.6k | return r; |
118 | 82.6k | } |
119 | | |
120 | 123 | static EvalResult eval_err(char *msg) { |
121 | 123 | EvalResult r; |
122 | 123 | r.ok = false; |
123 | 123 | r.value = value_unit(); |
124 | 123 | r.error = msg; |
125 | 123 | r.cf.tag = CF_NONE; |
126 | 123 | return r; |
127 | 123 | } |
128 | | |
129 | 810 | static EvalResult eval_signal(ControlFlowTag tag, LatValue v) { |
130 | 810 | EvalResult r; |
131 | 810 | r.ok = false; |
132 | 810 | r.value = value_unit(); |
133 | 810 | r.error = NULL; |
134 | 810 | r.cf.tag = tag; |
135 | 810 | r.cf.value = v; |
136 | 810 | return r; |
137 | 810 | } |
138 | | |
139 | 84.3k | #define IS_OK(r) ((r).ok) |
140 | 2.77k | #define IS_ERR(r) (!(r).ok && (r).error != NULL) |
141 | 21.0k | #define IS_SIGNAL(r) (!(r).ok && (r).error == NULL) |
142 | | |
143 | | /* ── Shadow stack macros ── */ |
144 | 39.2k | #define GC_PUSH(ev, vptr) lat_vec_push(&(ev)->gc_roots, &(LatValue*){(vptr)}) |
145 | 38.4k | #define GC_POP(ev) lat_vec_pop(&(ev)->gc_roots, NULL) |
146 | 23.2k | #define GC_POP_N(ev, n) do { for (size_t _i = 0; _i < (n); _i++) GC_POP(ev); } while(0) |
147 | | |
148 | | /* Forward declarations */ |
149 | | static EvalResult eval_expr_inner(Evaluator *ev, const Expr *expr); |
150 | | static EvalResult eval_stmt(Evaluator *ev, const Stmt *stmt); |
151 | | static EvalResult eval_block_stmts(Evaluator *ev, Stmt **stmts, size_t count); |
152 | | |
153 | | /* eval_expr now directly calls eval_expr_inner; temporaries are protected |
154 | | * by GC_PUSH/GC_POP at individual expression sites instead of the blunt |
155 | | * gc_inhibit hammer. */ |
156 | 68.3k | static inline EvalResult eval_expr(Evaluator *ev, const Expr *expr) { |
157 | 68.3k | return eval_expr_inner(ev, expr); |
158 | 68.3k | } |
159 | | |
160 | | /* ── Garbage Collector ── */ |
161 | | |
162 | | /* Forward declaration for mutual recursion with gc_mark_env_value */ |
163 | | static void gc_mark_value(FluidHeap *fh, LatValue *v, LatVec *reachable_regions); |
164 | | |
165 | | /* Callback for env_iter_values to mark each value */ |
166 | | typedef struct { |
167 | | FluidHeap *fh; |
168 | | LatVec *reachable_regions; |
169 | | } GcMarkCtx; |
170 | | |
171 | 30.7k | static void gc_mark_env_value(LatValue *v, void *ctx) { |
172 | 30.7k | GcMarkCtx *mc = (GcMarkCtx *)ctx; |
173 | 30.7k | gc_mark_value(mc->fh, v, mc->reachable_regions); |
174 | 30.7k | } |
175 | | |
176 | | /* |
177 | | * Mark a single LatValue as reachable, recursively marking contained |
178 | | * heap pointers in the fluid heap. Collects reachable crystal region |
179 | | * IDs into the supplied vector. |
180 | | */ |
181 | 144k | static void gc_mark_value(FluidHeap *fh, LatValue *v, LatVec *reachable_regions) { |
182 | | /* Arena-backed values: record the region as reachable and skip traversal. |
183 | | * All child pointers reside in the same region, so no fluid marking needed. |
184 | | * Note: compiled bytecode closures repurpose region_id as upvalue count, |
185 | | * so exclude them from this check. */ |
186 | 144k | if (v->region_id != REGION_NONE && v->region_id != REGION_EPHEMERAL) { |
187 | 20.0k | bool is_compiled_closure = (v->type == VAL_CLOSURE && |
188 | 20.0k | v->as.closure.body == NULL && |
189 | 20.0k | v->as.closure.native_fn != NULL); |
190 | 20.0k | if (!is_compiled_closure) { |
191 | 20.0k | lat_vec_push(reachable_regions, &v->region_id); |
192 | 20.0k | return; |
193 | 20.0k | } |
194 | 20.0k | } |
195 | 124k | switch (v->type) { |
196 | 415 | case VAL_STR: |
197 | 415 | if (v->as.str_val) |
198 | 415 | fluid_mark(fh, v->as.str_val); |
199 | 415 | break; |
200 | 12.3k | case VAL_ARRAY: |
201 | 12.3k | if (v->as.array.elems) { |
202 | 12.3k | fluid_mark(fh, v->as.array.elems); |
203 | 101k | for (size_t i = 0; i < v->as.array.len; i++) |
204 | 88.9k | gc_mark_value(fh, &v->as.array.elems[i], reachable_regions); |
205 | 12.3k | } |
206 | 12.3k | break; |
207 | 624 | case VAL_STRUCT: |
208 | 624 | if (v->as.strct.name) fluid_mark(fh, v->as.strct.name); |
209 | 624 | if (v->as.strct.field_names) { |
210 | 624 | fluid_mark(fh, v->as.strct.field_names); |
211 | 1.88k | for (size_t i = 0; i < v->as.strct.field_count; i++) { |
212 | 1.25k | if (v->as.strct.field_names[i]) |
213 | 1.25k | fluid_mark(fh, v->as.strct.field_names[i]); |
214 | 1.25k | } |
215 | 624 | } |
216 | 624 | if (v->as.strct.field_values) { |
217 | 624 | fluid_mark(fh, v->as.strct.field_values); |
218 | 1.88k | for (size_t i = 0; i < v->as.strct.field_count; i++) |
219 | 1.25k | gc_mark_value(fh, &v->as.strct.field_values[i], reachable_regions); |
220 | 624 | } |
221 | 624 | break; |
222 | 109 | case VAL_CLOSURE: |
223 | 109 | if (v->as.closure.param_names) { |
224 | 109 | fluid_mark(fh, v->as.closure.param_names); |
225 | 218 | for (size_t i = 0; i < v->as.closure.param_count; i++) { |
226 | 109 | if (v->as.closure.param_names[i]) |
227 | 109 | fluid_mark(fh, v->as.closure.param_names[i]); |
228 | 109 | } |
229 | 109 | } |
230 | | /* Mark captured env's values recursively */ |
231 | 109 | if (v->as.closure.captured_env) { |
232 | 109 | Env *cenv = v->as.closure.captured_env; |
233 | 109 | GcMarkCtx cctx = { fh, reachable_regions }; |
234 | 109 | env_iter_values(cenv, gc_mark_env_value, &cctx); |
235 | 109 | } |
236 | 109 | break; |
237 | 0 | case VAL_MAP: |
238 | 0 | if (v->as.map.map) { |
239 | 0 | fluid_mark(fh, v->as.map.map); |
240 | 0 | for (size_t i = 0; i < v->as.map.map->cap; i++) { |
241 | 0 | if (v->as.map.map->entries[i].state == MAP_OCCUPIED) { |
242 | 0 | gc_mark_value(fh, (LatValue *)v->as.map.map->entries[i].value, reachable_regions); |
243 | 0 | } |
244 | 0 | } |
245 | 0 | } |
246 | 0 | break; |
247 | 0 | case VAL_ENUM: |
248 | 0 | if (v->as.enm.enum_name) fluid_mark(fh, v->as.enm.enum_name); |
249 | 0 | if (v->as.enm.variant_name) fluid_mark(fh, v->as.enm.variant_name); |
250 | 0 | if (v->as.enm.payload) { |
251 | 0 | fluid_mark(fh, v->as.enm.payload); |
252 | 0 | for (size_t i = 0; i < v->as.enm.payload_count; i++) |
253 | 0 | gc_mark_value(fh, &v->as.enm.payload[i], reachable_regions); |
254 | 0 | } |
255 | 0 | break; |
256 | 0 | case VAL_SET: |
257 | 0 | if (v->as.set.map) { |
258 | 0 | fluid_mark(fh, v->as.set.map); |
259 | 0 | if (v->as.set.map->entries) { |
260 | 0 | fluid_mark(fh, v->as.set.map->entries); |
261 | 0 | for (size_t i = 0; i < v->as.set.map->cap; i++) { |
262 | 0 | if (v->as.set.map->entries[i].state == MAP_OCCUPIED) { |
263 | 0 | fluid_mark(fh, v->as.set.map->entries[i].key); |
264 | 0 | fluid_mark(fh, v->as.set.map->entries[i].value); |
265 | 0 | gc_mark_value(fh, (LatValue *)v->as.set.map->entries[i].value, reachable_regions); |
266 | 0 | } |
267 | 0 | } |
268 | 0 | } |
269 | 0 | } |
270 | 0 | break; |
271 | 110k | default: |
272 | 110k | break; |
273 | 124k | } |
274 | 124k | } |
275 | | |
276 | | #ifndef NDEBUG |
277 | | #include <assert.h> |
278 | | /* |
279 | | * Debug assertion: verify that no crystal value's heap pointers appear |
280 | | * in the fluid alloc list. Violation would mean freeze didn't properly |
281 | | * untrack the pointer, risking a double-free during sweep. |
282 | | */ |
283 | 13.8k | static bool ptr_in_fluid(FluidHeap *fh, void *ptr) { |
284 | 13.8k | if (!ptr) return false; |
285 | 47.7k | for (FluidAlloc *a = fh->allocs; a; a = a->next) { |
286 | 33.8k | if (a->ptr == ptr) return true; |
287 | 33.8k | } |
288 | 13.8k | return false; |
289 | 13.8k | } |
290 | | |
291 | 29.9k | static void assert_crystal_not_fluid(LatValue *v, void *ctx) { |
292 | 29.9k | FluidHeap *fh = (FluidHeap *)ctx; |
293 | 29.9k | if (v->phase != VTAG_CRYSTAL || v->region_id == (size_t)-1) return; |
294 | 6.67k | switch (v->type) { |
295 | 0 | case VAL_STR: |
296 | 0 | assert(!ptr_in_fluid(fh, v->as.str_val) && |
297 | 0 | "crystal string in fluid heap"); |
298 | 0 | break; |
299 | 357 | case VAL_ARRAY: |
300 | 357 | assert(!ptr_in_fluid(fh, v->as.array.elems) && |
301 | 357 | "crystal array elems in fluid heap"); |
302 | 357 | break; |
303 | 357 | case VAL_STRUCT: |
304 | 301 | assert(!ptr_in_fluid(fh, v->as.strct.name) && |
305 | 301 | "crystal struct name in fluid heap"); |
306 | 301 | assert(!ptr_in_fluid(fh, v->as.strct.field_names) && |
307 | 301 | "crystal struct field_names in fluid heap"); |
308 | 301 | assert(!ptr_in_fluid(fh, v->as.strct.field_values) && |
309 | 301 | "crystal struct field_values in fluid heap"); |
310 | 903 | for (size_t i = 0; i < v->as.strct.field_count; i++) { |
311 | 602 | assert(!ptr_in_fluid(fh, v->as.strct.field_names[i]) && |
312 | 602 | "crystal struct field_name string in fluid heap"); |
313 | 602 | } |
314 | 301 | break; |
315 | 6.01k | case VAL_CLOSURE: |
316 | 6.01k | assert(!ptr_in_fluid(fh, v->as.closure.param_names) && |
317 | 6.01k | "crystal closure param_names in fluid heap"); |
318 | 12.0k | for (size_t i = 0; i < v->as.closure.param_count; i++) { |
319 | 6.01k | assert(!ptr_in_fluid(fh, v->as.closure.param_names[i]) && |
320 | 6.01k | "crystal closure param_name string in fluid heap"); |
321 | 6.01k | } |
322 | 6.01k | break; |
323 | 6.01k | case VAL_MAP: |
324 | 0 | if (v->as.map.map) { |
325 | 0 | assert(!ptr_in_fluid(fh, v->as.map.map) && |
326 | 0 | "crystal map struct in fluid heap"); |
327 | 0 | assert(!ptr_in_fluid(fh, v->as.map.map->entries) && |
328 | 0 | "crystal map entries in fluid heap"); |
329 | 0 | } |
330 | 0 | break; |
331 | 0 | case VAL_SET: |
332 | 0 | if (v->as.set.map) { |
333 | 0 | assert(!ptr_in_fluid(fh, v->as.set.map) && |
334 | 0 | "crystal set struct in fluid heap"); |
335 | 0 | assert(!ptr_in_fluid(fh, v->as.set.map->entries) && |
336 | 0 | "crystal set entries in fluid heap"); |
337 | 0 | } |
338 | 0 | break; |
339 | 1 | default: |
340 | 1 | break; |
341 | 6.67k | } |
342 | 6.67k | } |
343 | | |
344 | 7.37k | static void assert_dual_heap_invariant(Evaluator *ev) { |
345 | 7.37k | env_iter_values(ev->env, assert_crystal_not_fluid, ev->heap->fluid); |
346 | 7.47k | for (size_t i = 0; i < ev->saved_envs.len; i++) { |
347 | 100 | Env **ep = lat_vec_get(&ev->saved_envs, i); |
348 | 100 | env_iter_values(*ep, assert_crystal_not_fluid, ev->heap->fluid); |
349 | 100 | } |
350 | 7.37k | } |
351 | | #endif /* NDEBUG */ |
352 | | |
353 | | /* |
354 | | * Run a full GC cycle: mark all roots, sweep unreachable. |
355 | | */ |
356 | 7.37k | static void gc_cycle(Evaluator *ev) { |
357 | 7.37k | FluidHeap *fh = ev->heap->fluid; |
358 | 7.37k | LatVec reachable_regions = lat_vec_new(sizeof(RegionId)); |
359 | | |
360 | | /* 0. Advance epoch — groups frozen values by GC generation */ |
361 | 7.37k | if (!ev->no_regions) |
362 | 7.37k | region_advance_epoch(ev->heap->regions); |
363 | | |
364 | | /* 1. Clear all marks */ |
365 | 7.37k | fluid_unmark_all(fh); |
366 | | |
367 | | /* 2. Mark roots from environment */ |
368 | 7.37k | GcMarkCtx ctx = { fh, &reachable_regions }; |
369 | 7.37k | env_iter_values(ev->env, gc_mark_env_value, &ctx); |
370 | | |
371 | | /* 3. Mark roots from shadow stack */ |
372 | 30.7k | for (size_t i = 0; i < ev->gc_roots.len; i++) { |
373 | 23.3k | LatValue **vp = lat_vec_get(&ev->gc_roots, i); |
374 | 23.3k | if (*vp) gc_mark_value(fh, *vp, &reachable_regions); |
375 | 23.3k | } |
376 | | |
377 | | /* 4. Mark values from saved caller environments (closure env swap) */ |
378 | 7.47k | for (size_t i = 0; i < ev->saved_envs.len; i++) { |
379 | 100 | Env **ep = lat_vec_get(&ev->saved_envs, i); |
380 | 100 | env_iter_values(*ep, gc_mark_env_value, &ctx); |
381 | 100 | } |
382 | | |
383 | | /* 5. Sweep unmarked fluid allocations */ |
384 | 7.37k | size_t fluid_before = fh->total_bytes; |
385 | 7.37k | size_t swept_fluid = fluid_sweep(fh); |
386 | 7.37k | ev->stats.gc_bytes_swept += fluid_before - fh->total_bytes; |
387 | | |
388 | | /* 6. Collect unreachable crystal regions */ |
389 | 7.37k | size_t swept_regions = 0; |
390 | 7.37k | if (!ev->no_regions) { |
391 | 7.37k | swept_regions = region_collect( |
392 | 7.37k | ev->heap->regions, |
393 | 7.37k | (RegionId *)reachable_regions.data, |
394 | 7.37k | reachable_regions.len); |
395 | 7.37k | } |
396 | | |
397 | | /* 7. Update stats */ |
398 | 7.37k | ev->stats.gc_cycles++; |
399 | 7.37k | ev->stats.gc_swept_fluid += swept_fluid; |
400 | 7.37k | ev->stats.gc_swept_regions += swept_regions; |
401 | | |
402 | 7.37k | lat_vec_free(&reachable_regions); |
403 | | |
404 | 7.37k | #ifndef NDEBUG |
405 | | /* 8. Verify dual-heap invariant: no crystal pointers in fluid heap */ |
406 | 7.37k | if (!ev->no_regions) |
407 | 7.37k | assert_dual_heap_invariant(ev); |
408 | 7.37k | #endif |
409 | 7.37k | } |
410 | | |
411 | | /* |
412 | | * Maybe trigger GC if heap exceeds threshold. |
413 | | * Called after allocations. |
414 | | */ |
415 | 14.9k | static void gc_maybe_collect(Evaluator *ev) { |
416 | 14.9k | if (ev->gc_stress || |
417 | 14.9k | ev->heap->fluid->total_bytes >= ev->heap->fluid->gc_threshold) { |
418 | 7.37k | uint64_t t0 = now_ns(); |
419 | 7.37k | gc_cycle(ev); |
420 | 7.37k | ev->stats.gc_total_ns += now_ns() - t0; |
421 | 7.37k | } |
422 | 14.9k | } |
423 | | |
424 | | /* |
425 | | * Recursively set region_id on a value and all nested values. |
426 | | * Must walk into closure captured environments so that GC knows |
427 | | * every arena pointer belongs to this region. |
428 | | */ |
429 | | static void set_region_id_env(Env *env, RegionId rid); |
430 | | |
431 | 1.01k | static void set_region_id_recursive(LatValue *v, RegionId rid) { |
432 | 1.01k | v->region_id = rid; |
433 | 1.01k | switch (v->type) { |
434 | 103 | case VAL_ARRAY: |
435 | 419 | for (size_t i = 0; i < v->as.array.len; i++) |
436 | 316 | set_region_id_recursive(&v->as.array.elems[i], rid); |
437 | 103 | break; |
438 | 159 | case VAL_STRUCT: |
439 | 479 | for (size_t i = 0; i < v->as.strct.field_count; i++) |
440 | 320 | set_region_id_recursive(&v->as.strct.field_values[i], rid); |
441 | 159 | break; |
442 | 13 | case VAL_CLOSURE: |
443 | 13 | if (v->as.closure.captured_env) |
444 | 13 | set_region_id_env(v->as.closure.captured_env, rid); |
445 | 13 | break; |
446 | 22 | case VAL_MAP: |
447 | 22 | if (v->as.map.map) { |
448 | 374 | for (size_t i = 0; i < v->as.map.map->cap; i++) { |
449 | 352 | if (v->as.map.map->entries[i].state == MAP_OCCUPIED) { |
450 | 15 | LatValue *mv = (LatValue *)v->as.map.map->entries[i].value; |
451 | 15 | set_region_id_recursive(mv, rid); |
452 | 15 | } |
453 | 352 | } |
454 | 22 | } |
455 | 22 | break; |
456 | 0 | case VAL_ENUM: |
457 | 0 | for (size_t i = 0; i < v->as.enm.payload_count; i++) |
458 | 0 | set_region_id_recursive(&v->as.enm.payload[i], rid); |
459 | 0 | break; |
460 | 0 | case VAL_SET: |
461 | 0 | if (v->as.set.map) { |
462 | 0 | for (size_t i = 0; i < v->as.set.map->cap; i++) { |
463 | 0 | if (v->as.set.map->entries[i].state == MAP_OCCUPIED) { |
464 | 0 | LatValue *sv = (LatValue *)v->as.set.map->entries[i].value; |
465 | 0 | set_region_id_recursive(sv, rid); |
466 | 0 | } |
467 | 0 | } |
468 | 0 | } |
469 | 0 | break; |
470 | 718 | default: |
471 | 718 | break; |
472 | 1.01k | } |
473 | 1.01k | } |
474 | | |
475 | 19 | static void set_region_id_env_value(LatValue *v, void *ctx) { |
476 | 19 | RegionId rid = *(RegionId *)ctx; |
477 | 19 | set_region_id_recursive(v, rid); |
478 | 19 | } |
479 | | |
480 | 13 | static void set_region_id_env(Env *env, RegionId rid) { |
481 | 13 | env_iter_values(env, set_region_id_env_value, &rid); |
482 | 13 | } |
483 | | |
484 | | /* |
485 | | * Freeze support: deep-clone value into a new arena-backed region, |
486 | | * set region_id recursively, free the original fluid-heap value, |
487 | | * and replace it with the arena clone. |
488 | | * |
489 | | * In no-regions baseline mode, crystal values stay in the fluid heap. |
490 | | */ |
491 | 345 | static void freeze_to_region(Evaluator *ev, LatValue *v) { |
492 | 345 | if (ev->no_regions) return; |
493 | | |
494 | 345 | CrystalRegion *region = region_create(ev->heap->regions); |
495 | | |
496 | 345 | value_set_arena(region); |
497 | 345 | LatValue clone = value_deep_clone(v); |
498 | 345 | value_set_arena(NULL); |
499 | | |
500 | 345 | ev->heap->regions->cumulative_data_bytes += region->total_bytes; |
501 | | |
502 | 345 | set_region_id_recursive(&clone, region->id); |
503 | | |
504 | 345 | value_free(v); |
505 | 345 | *v = clone; |
506 | 345 | } |
507 | | |
508 | | /* Record a history snapshot for a tracked variable */ |
509 | 5.63k | static void record_history(Evaluator *ev, const char *name) { |
510 | 5.63k | for (size_t i = 0; i < ev->tracked_count; i++) { |
511 | 17 | if (strcmp(ev->tracked_vars[i].name, name) != 0) continue; |
512 | 17 | VariableHistory *vh = &ev->tracked_vars[i].history; |
513 | 17 | LatValue cur; |
514 | 17 | if (!env_get(ev->env, name, &cur)) return; |
515 | 17 | if (vh->count >= vh->cap) { |
516 | 0 | vh->cap = vh->cap ? vh->cap * 2 : 8; |
517 | 0 | vh->snapshots = realloc(vh->snapshots, vh->cap * sizeof(HistorySnapshot)); |
518 | 0 | } |
519 | 17 | const char *phase = builtin_phase_of_str(&cur); |
520 | 17 | vh->snapshots[vh->count].phase_name = strdup(phase); |
521 | 17 | vh->snapshots[vh->count].value = value_deep_clone(&cur); |
522 | 17 | vh->snapshots[vh->count].line = 0; |
523 | 17 | vh->snapshots[vh->count].fn_name = NULL; |
524 | 17 | vh->count++; |
525 | 17 | value_free(&cur); |
526 | 17 | return; |
527 | 17 | } |
528 | 5.63k | } |
529 | | |
530 | | /* Forward declaration for fire_reactions */ |
531 | | static EvalResult call_closure(Evaluator *ev, char **params, size_t param_count, |
532 | | const Expr *body, Env *closure_env, LatValue *args, size_t arg_count, |
533 | | Expr **default_values, bool has_variadic); |
534 | | |
535 | | /// @builtin react(var: Ident, callback: Closure) -> Unit |
536 | | /// @category Phase Reactions |
537 | | /// Register a callback that fires when a variable's phase changes. |
538 | | /// @example react(data, |phase, val| { print(phase) }) |
539 | | |
540 | | /// @builtin unreact(var: Ident) -> Unit |
541 | | /// @category Phase Reactions |
542 | | /// Remove all phase reaction callbacks from a variable. |
543 | | /// @example unreact(data) |
544 | | |
545 | 478 | static EvalResult fire_reactions(Evaluator *ev, const char *var_name, const char *phase_name) { |
546 | 479 | for (size_t i = 0; i < ev->reaction_count; i++) { |
547 | 9 | if (strcmp(ev->reactions[i].var_name, var_name) != 0) continue; |
548 | 8 | LatValue cur; |
549 | 8 | if (!env_get(ev->env, var_name, &cur)) return eval_ok(value_unit()); |
550 | 16 | for (size_t j = 0; j < ev->reactions[i].cb_count; j++) { |
551 | 9 | LatValue *cb = &ev->reactions[i].callbacks[j]; |
552 | 9 | LatValue args[2]; |
553 | 9 | args[0] = value_string(phase_name); |
554 | 9 | args[1] = value_deep_clone(&cur); |
555 | 9 | EvalResult r = call_closure(ev, cb->as.closure.param_names, |
556 | 9 | cb->as.closure.param_count, cb->as.closure.body, |
557 | 9 | cb->as.closure.captured_env, args, 2, |
558 | 9 | cb->as.closure.default_values, cb->as.closure.has_variadic); |
559 | 9 | if (!IS_OK(r)) { |
560 | 1 | value_free(&cur); |
561 | 1 | char *err = NULL; |
562 | 1 | (void)asprintf(&err, "reaction error: %s", r.error); |
563 | 1 | free(r.error); |
564 | 1 | return eval_err(err); |
565 | 1 | } |
566 | 8 | value_free(&r.value); |
567 | 8 | } |
568 | 7 | value_free(&cur); |
569 | 7 | return eval_ok(value_unit()); |
570 | 8 | } |
571 | 470 | return eval_ok(value_unit()); |
572 | 478 | } |
573 | | |
574 | | /* Cascade freeze through bonded variables */ |
575 | | /* Returns NULL on success, heap-allocated error string on failure */ |
576 | 284 | static char *freeze_cascade(Evaluator *ev, const char *target_name) { |
577 | 297 | for (size_t bi = 0; bi < ev->bond_count; bi++) { |
578 | 24 | if (strcmp(ev->bonds[bi].target, target_name) != 0) continue; |
579 | | /* Found bond entry for target — process all deps by strategy */ |
580 | 23 | for (size_t di = 0; di < ev->bonds[bi].dep_count; di++) { |
581 | 13 | const char *dep = ev->bonds[bi].deps[di]; |
582 | 13 | const char *strategy = ev->bonds[bi].dep_strategies ? ev->bonds[bi].dep_strategies[di] : "mirror"; |
583 | 13 | LatValue dval; |
584 | 13 | if (!env_get(ev->env, dep, &dval)) continue; /* variable gone */ |
585 | 13 | if (dval.type == VAL_CHANNEL) { value_free(&dval); continue; } |
586 | | |
587 | 13 | if (strcmp(strategy, "mirror") == 0) { |
588 | 10 | if (dval.phase == VTAG_CRYSTAL) { value_free(&dval); continue; } |
589 | 10 | dval = value_freeze(dval); |
590 | 10 | freeze_to_region(ev, &dval); |
591 | 10 | env_set(ev->env, dep, dval); |
592 | 10 | EvalResult fr = fire_reactions(ev, dep, "crystal"); |
593 | 10 | if (!IS_OK(fr)) free(fr.error); |
594 | 10 | char *err = freeze_cascade(ev, dep); |
595 | 10 | if (err) return err; |
596 | 10 | } else if (strcmp(strategy, "inverse") == 0) { |
597 | | /* Inverse: thaw the dep when target freezes */ |
598 | 1 | if (dval.phase != VTAG_CRYSTAL && dval.phase != VTAG_SUBLIMATED) { value_free(&dval); continue; } |
599 | 1 | LatValue thawed = value_thaw(&dval); |
600 | 1 | value_free(&dval); |
601 | 1 | env_set(ev->env, dep, thawed); |
602 | 1 | EvalResult fr = fire_reactions(ev, dep, "fluid"); |
603 | 1 | if (!IS_OK(fr)) free(fr.error); |
604 | 2 | } else if (strcmp(strategy, "gate") == 0) { |
605 | | /* Gate: dep must already be crystal for target to freeze */ |
606 | 2 | if (dval.phase != VTAG_CRYSTAL) { |
607 | 1 | value_free(&dval); |
608 | 1 | char *err = NULL; |
609 | 1 | (void)asprintf(&err, "gate bond: '%s' must be crystal before '%s' can freeze", dep, target_name); |
610 | 1 | return err; |
611 | 1 | } |
612 | 1 | value_free(&dval); |
613 | 1 | } else { |
614 | 0 | value_free(&dval); |
615 | 0 | } |
616 | 13 | } |
617 | | /* Consume the bond entry */ |
618 | 22 | for (size_t di = 0; di < ev->bonds[bi].dep_count; di++) { |
619 | 12 | free(ev->bonds[bi].deps[di]); |
620 | 12 | if (ev->bonds[bi].dep_strategies) free(ev->bonds[bi].dep_strategies[di]); |
621 | 12 | } |
622 | 10 | free(ev->bonds[bi].deps); |
623 | 10 | free(ev->bonds[bi].dep_strategies); |
624 | 10 | free(ev->bonds[bi].target); |
625 | 10 | ev->bonds[bi] = ev->bonds[--ev->bond_count]; |
626 | 10 | break; |
627 | 11 | } |
628 | 283 | return NULL; |
629 | 284 | } |
630 | | |
631 | | /* |
632 | | * Resolve a mutable pointer to a LatValue from an lvalue expression. |
633 | | * Walks chains of field_access and index expressions to find the |
634 | | * actual storage location in the environment. Returns NULL + sets *err on failure. |
635 | | */ |
636 | 1.22k | static LatValue *resolve_lvalue(Evaluator *ev, const Expr *expr, char **err) { |
637 | 1.22k | if (expr->tag == EXPR_IDENT) { |
638 | 1.23k | for (size_t s = ev->env->count; s > 0; s--) { |
639 | 1.23k | LatValue *v = lat_map_get(&ev->env->scopes[s-1], expr->as.str_val); |
640 | 1.23k | if (v) return v; |
641 | 1.23k | } |
642 | 0 | (void)asprintf(err, "undefined variable '%s'", expr->as.str_val); |
643 | 0 | return NULL; |
644 | 1.16k | } |
645 | 59 | if (expr->tag == EXPR_FIELD_ACCESS) { |
646 | 20 | LatValue *parent = resolve_lvalue(ev, expr->as.field_access.object, err); |
647 | 20 | if (!parent) return NULL; |
648 | 20 | if (parent->type != VAL_STRUCT) { |
649 | 0 | (void)asprintf(err, "cannot access field '%s' on %s", |
650 | 0 | expr->as.field_access.field, value_type_name(parent)); |
651 | 0 | return NULL; |
652 | 0 | } |
653 | 29 | for (size_t i = 0; i < parent->as.strct.field_count; i++) { |
654 | 29 | if (strcmp(parent->as.strct.field_names[i], expr->as.field_access.field) == 0) { |
655 | 20 | return &parent->as.strct.field_values[i]; |
656 | 20 | } |
657 | 29 | } |
658 | 0 | (void)asprintf(err, "struct has no field '%s'", expr->as.field_access.field); |
659 | 0 | return NULL; |
660 | 20 | } |
661 | 39 | if (expr->tag == EXPR_INDEX) { |
662 | | /* Evaluate the index expression BEFORE resolving the parent lvalue. |
663 | | * eval_expr may trigger GC or scope-map mutations (lat_map_set → |
664 | | * rehash), which would invalidate any raw pointer returned by |
665 | | * resolve_lvalue. By evaluating first we avoid stale pointers. */ |
666 | 39 | EvalResult idxr = eval_expr(ev, expr->as.index.index); |
667 | 39 | if (!IS_OK(idxr)) { *err = idxr.error; return NULL; } |
668 | | |
669 | 39 | LatValue *parent = resolve_lvalue(ev, expr->as.index.object, err); |
670 | 39 | if (!parent) { value_free(&idxr.value); return NULL; } |
671 | | |
672 | | /* Ref unwrap: delegate indexing to the inner value */ |
673 | 39 | if (parent->type == VAL_REF) parent = &parent->as.ref.ref->value; |
674 | | |
675 | 39 | if (parent->type == VAL_MAP) { |
676 | 30 | if (idxr.value.type != VAL_STR) { |
677 | 0 | value_free(&idxr.value); |
678 | 0 | *err = strdup("map key must be a string"); |
679 | 0 | return NULL; |
680 | 0 | } |
681 | 30 | const char *key = idxr.value.as.str_val; |
682 | | /* Auto-vivify: if key doesn't exist, create it with unit */ |
683 | 30 | if (!lat_map_contains(parent->as.map.map, key)) { |
684 | 25 | LatValue unit = value_unit(); |
685 | 25 | lat_map_set(parent->as.map.map, key, &unit); |
686 | 25 | } |
687 | 30 | LatValue *target = (LatValue *)lat_map_get(parent->as.map.map, key); |
688 | 30 | value_free(&idxr.value); |
689 | 30 | return target; |
690 | 30 | } |
691 | 9 | if (parent->type == VAL_ARRAY) { |
692 | 9 | if (idxr.value.type != VAL_INT) { |
693 | 0 | value_free(&idxr.value); |
694 | 0 | *err = strdup("array index must be an integer"); |
695 | 0 | return NULL; |
696 | 0 | } |
697 | 9 | size_t idx = (size_t)idxr.value.as.int_val; |
698 | 9 | value_free(&idxr.value); |
699 | 9 | if (idx >= parent->as.array.len) { |
700 | 0 | (void)asprintf(err, "index %zu out of bounds (length %zu)", |
701 | 0 | idx, parent->as.array.len); |
702 | 0 | return NULL; |
703 | 0 | } |
704 | 9 | return &parent->as.array.elems[idx]; |
705 | 9 | } |
706 | 0 | value_free(&idxr.value); |
707 | 0 | (void)asprintf(err, "cannot index into %s", value_type_name(parent)); |
708 | 0 | return NULL; |
709 | 9 | } |
710 | 0 | *err = strdup("invalid lvalue expression"); |
711 | 0 | return NULL; |
712 | 39 | } |
713 | | static EvalResult eval_method_call(Evaluator *ev, LatValue obj, const char *method, |
714 | | LatValue *args, size_t arg_count); |
715 | | |
716 | | /* ── FnDecl lookup helpers ── */ |
717 | | /* We store FnDecl* pointers in the map */ |
718 | | |
719 | 1.70k | static FnDecl *find_fn(Evaluator *ev, const char *name) { |
720 | 1.70k | FnDecl **ptr = lat_map_get(&ev->fn_defs, name); |
721 | 1.70k | return ptr ? *ptr : NULL; |
722 | 1.70k | } |
723 | | |
724 | | /* ── Phase helpers for constraints & dispatch ── */ |
725 | | |
726 | 3 | static const char *phase_tag_name(PhaseTag p) { |
727 | 3 | switch (p) { |
728 | 1 | case VTAG_FLUID: return "fluid (flux)"; |
729 | 2 | case VTAG_CRYSTAL: return "crystal (fix)"; |
730 | 0 | case VTAG_UNPHASED: return "unphased"; |
731 | 0 | case VTAG_SUBLIMATED: return "sublimated"; |
732 | 3 | } |
733 | 0 | return "unknown"; |
734 | 3 | } |
735 | | |
736 | 3 | static const char *ast_phase_name(AstPhase p) { |
737 | 3 | switch (p) { |
738 | 2 | case PHASE_FLUID: return "flux"; |
739 | 1 | case PHASE_CRYSTAL: return "fix"; |
740 | 0 | case PHASE_UNSPECIFIED: return "unspecified"; |
741 | 3 | } |
742 | 0 | return "unknown"; |
743 | 3 | } |
744 | | |
745 | 16 | static bool phase_compatible(PhaseTag value_phase, AstPhase param_phase) { |
746 | 16 | switch (param_phase) { |
747 | 9 | case PHASE_FLUID: return value_phase != VTAG_CRYSTAL; |
748 | 6 | case PHASE_CRYSTAL: return value_phase != VTAG_FLUID; |
749 | 1 | case PHASE_UNSPECIFIED: return true; |
750 | 16 | } |
751 | 0 | return true; |
752 | 16 | } |
753 | | |
754 | 1.11k | static bool type_matches_value(const LatValue *val, const TypeExpr *te) { |
755 | 1.11k | if (!te || !te->name) return true; /* no annotation = Any */ |
756 | 1.11k | if (strcmp(te->name, "Any") == 0 || strcmp(te->name, "any") == 0) return true; |
757 | 585 | if (te->kind == TYPE_ARRAY) { |
758 | 0 | if (val->type != VAL_ARRAY) return false; |
759 | 0 | if (!te->inner) return true; /* [Any] or unspecified inner */ |
760 | 0 | for (size_t i = 0; i < val->as.array.len; i++) { |
761 | 0 | if (!type_matches_value(&val->as.array.elems[i], te->inner)) |
762 | 0 | return false; |
763 | 0 | } |
764 | 0 | return true; |
765 | 0 | } |
766 | | /* Named types */ |
767 | 585 | const char *n = te->name; |
768 | 585 | if (strcmp(n, "Int") == 0) return val->type == VAL_INT; |
769 | 511 | if (strcmp(n, "Float") == 0) return val->type == VAL_FLOAT; |
770 | 511 | if (strcmp(n, "String") == 0) return val->type == VAL_STR; |
771 | 458 | if (strcmp(n, "Bool") == 0) return val->type == VAL_BOOL; |
772 | 457 | if (strcmp(n, "Nil") == 0) return val->type == VAL_NIL; |
773 | 457 | if (strcmp(n, "Map") == 0) return val->type == VAL_MAP; |
774 | 75 | if (strcmp(n, "Array") == 0) return val->type == VAL_ARRAY; |
775 | 40 | if (strcmp(n, "Fn") == 0 || strcmp(n, "Closure") == 0) return val->type == VAL_CLOSURE; |
776 | 11 | if (strcmp(n, "Channel") == 0) return val->type == VAL_CHANNEL; |
777 | 11 | if (strcmp(n, "Range") == 0) return val->type == VAL_RANGE; |
778 | 11 | if (strcmp(n, "Set") == 0) return val->type == VAL_SET; |
779 | 11 | if (strcmp(n, "Tuple") == 0) return val->type == VAL_TUPLE; |
780 | 11 | if (strcmp(n, "Buffer") == 0) return val->type == VAL_BUFFER; |
781 | 11 | if (strcmp(n, "Ref") == 0) return val->type == VAL_REF; |
782 | 11 | if (strcmp(n, "Number") == 0) return val->type == VAL_INT || val->type == VAL_FLOAT; |
783 | | /* Struct name check */ |
784 | 6 | if (val->type == VAL_STRUCT && val->as.strct.name) |
785 | 5 | return strcmp(val->as.strct.name, n) == 0; |
786 | | /* Enum name check */ |
787 | 1 | if (val->type == VAL_ENUM && val->as.enm.enum_name) |
788 | 1 | return strcmp(val->as.enm.enum_name, n) == 0; |
789 | 0 | return false; |
790 | 1 | } |
791 | | |
792 | 4 | static const char *value_type_display(const LatValue *val) { |
793 | 4 | switch (val->type) { |
794 | 0 | case VAL_INT: return "Int"; |
795 | 0 | case VAL_FLOAT: return "Float"; |
796 | 0 | case VAL_BOOL: return "Bool"; |
797 | 3 | case VAL_STR: return "String"; |
798 | 0 | case VAL_ARRAY: return "Array"; |
799 | 1 | case VAL_STRUCT: return val->as.strct.name ? val->as.strct.name : "Struct"; |
800 | 0 | case VAL_CLOSURE: return "Fn"; |
801 | 0 | case VAL_UNIT: return "Unit"; |
802 | 0 | case VAL_NIL: return "Nil"; |
803 | 0 | case VAL_RANGE: return "Range"; |
804 | 0 | case VAL_MAP: return "Map"; |
805 | 0 | case VAL_CHANNEL: return "Channel"; |
806 | 0 | case VAL_ENUM: return val->as.enm.enum_name ? val->as.enm.enum_name : "Enum"; |
807 | 0 | case VAL_SET: return "Set"; |
808 | 0 | case VAL_TUPLE: return "Tuple"; |
809 | 0 | case VAL_BUFFER: return "Buffer"; |
810 | 0 | case VAL_REF: return "Ref"; |
811 | 4 | } |
812 | 0 | return "Unknown"; |
813 | 4 | } |
814 | | |
815 | 4 | static bool phase_signatures_match(const FnDecl *a, const FnDecl *b) { |
816 | 4 | if (a->param_count != b->param_count) return false; |
817 | 4 | for (size_t i = 0; i < a->param_count; i++) { |
818 | 3 | if (a->params[i].ty.phase != b->params[i].ty.phase) return false; |
819 | 3 | } |
820 | 1 | return true; |
821 | 4 | } |
822 | | |
823 | 3 | static FnDecl *resolve_overload(FnDecl *head, LatValue *args, size_t argc) { |
824 | 3 | FnDecl *best = NULL; |
825 | 3 | int best_score = -1; |
826 | | |
827 | 9 | for (FnDecl *cand = head; cand; cand = cand->next_overload) { |
828 | | /* Check arity */ |
829 | 6 | size_t required = 0; |
830 | 6 | bool has_variadic = false; |
831 | 12 | for (size_t i = 0; i < cand->param_count; i++) { |
832 | 6 | if (cand->params[i].is_variadic) has_variadic = true; |
833 | 6 | else if (!cand->params[i].default_value) required++; |
834 | 6 | } |
835 | 6 | size_t max_pos = has_variadic ? cand->param_count - 1 : cand->param_count; |
836 | 6 | if (argc < required || (!has_variadic && argc > max_pos)) continue; |
837 | | |
838 | | /* Check phase compatibility and compute score */ |
839 | 6 | bool compatible = true; |
840 | 6 | int score = 0; |
841 | 6 | size_t check_count = argc < cand->param_count ? argc : cand->param_count; |
842 | 10 | for (size_t i = 0; i < check_count; i++) { |
843 | 6 | if (cand->params[i].is_variadic) break; |
844 | 6 | AstPhase pp = cand->params[i].ty.phase; |
845 | 6 | PhaseTag vp = args[i].phase; |
846 | 6 | if (!phase_compatible(vp, pp)) { compatible = false; break; } |
847 | 4 | if (pp == PHASE_FLUID && vp == VTAG_FLUID) |
848 | 1 | score += 3; /* exact: fluid→flux */ |
849 | 3 | else if (pp == PHASE_CRYSTAL && vp == VTAG_CRYSTAL) |
850 | 1 | score += 3; /* exact: crystal→fix */ |
851 | 2 | else if (pp == PHASE_UNSPECIFIED && vp == VTAG_UNPHASED) |
852 | 1 | score += 2; /* exact: unphased→unspecified */ |
853 | 1 | else if (pp == PHASE_UNSPECIFIED) |
854 | 0 | score += 1; /* compatible: specific→unspecified */ |
855 | | /* else: unphased→specific = 0 (compatible but weakest) */ |
856 | 4 | } |
857 | 6 | if (!compatible) continue; |
858 | | |
859 | 4 | if (score >= best_score) { |
860 | 3 | best_score = score; |
861 | 3 | best = cand; |
862 | 3 | } |
863 | 4 | } |
864 | 3 | return best; |
865 | 3 | } |
866 | | |
867 | 2.66k | static void register_fn_overload(LatMap *fn_defs, FnDecl *new_fn) { |
868 | 2.66k | FnDecl **existing = lat_map_get(fn_defs, new_fn->name); |
869 | 2.66k | if (!existing) { |
870 | | /* No existing fn with this name — simple insert */ |
871 | 2.66k | lat_map_set(fn_defs, new_fn->name, &new_fn); |
872 | 2.66k | return; |
873 | 2.66k | } |
874 | | /* Check if same phase signature — replace */ |
875 | 4 | FnDecl *head = *existing; |
876 | 4 | if (phase_signatures_match(head, new_fn)) { |
877 | | /* Replace head: keep the chain */ |
878 | 1 | new_fn->next_overload = head->next_overload; |
879 | 1 | lat_map_set(fn_defs, new_fn->name, &new_fn); |
880 | 1 | return; |
881 | 1 | } |
882 | | /* Check rest of chain */ |
883 | 3 | for (FnDecl *prev = head; prev->next_overload; prev = prev->next_overload) { |
884 | 0 | if (phase_signatures_match(prev->next_overload, new_fn)) { |
885 | | /* Replace this link */ |
886 | 0 | new_fn->next_overload = prev->next_overload->next_overload; |
887 | 0 | prev->next_overload = new_fn; |
888 | 0 | return; |
889 | 0 | } |
890 | 0 | } |
891 | | /* Different phase signature — chain it */ |
892 | 3 | new_fn->next_overload = head; |
893 | 3 | lat_map_set(fn_defs, new_fn->name, &new_fn); |
894 | 3 | } |
895 | | |
896 | 197 | static StructDecl *find_struct(Evaluator *ev, const char *name) { |
897 | 197 | StructDecl **ptr = lat_map_get(&ev->struct_defs, name); |
898 | 197 | return ptr ? *ptr : NULL; |
899 | 197 | } |
900 | | |
901 | 488 | static EnumDecl *find_enum(Evaluator *ev, const char *name) { |
902 | 488 | EnumDecl **ptr = lat_map_get(&ev->enum_defs, name); |
903 | 488 | return ptr ? *ptr : NULL; |
904 | 488 | } |
905 | | |
906 | 14 | static VariantDecl *find_variant(EnumDecl *ed, const char *variant_name) { |
907 | 17 | for (size_t i = 0; i < ed->variant_count; i++) { |
908 | 17 | if (strcmp(ed->variants[i].name, variant_name) == 0) |
909 | 14 | return &ed->variants[i]; |
910 | 17 | } |
911 | 0 | return NULL; |
912 | 14 | } |
913 | | |
914 | | /* Find a pressure constraint for a variable (returns mode string or NULL) */ |
915 | 137 | static const char *find_pressure(Evaluator *ev, const char *var_name) { |
916 | 137 | for (size_t i = 0; i < ev->pressure_count; i++) { |
917 | 3 | if (strcmp(ev->pressures[i].var_name, var_name) == 0) |
918 | 3 | return ev->pressures[i].mode; |
919 | 3 | } |
920 | 134 | return NULL; |
921 | 137 | } |
922 | | |
923 | | /* Check if a pressure mode blocks a structural growth operation (push, insert, merge) */ |
924 | 132 | static bool pressure_blocks_grow(const char *mode) { |
925 | 132 | return mode && (strcmp(mode, "no_grow") == 0 || strcmp(mode, "no_resize") == 0); |
926 | 132 | } |
927 | | |
928 | | /* Check if a pressure mode blocks a structural shrink operation (pop, remove, remove_at) */ |
929 | 5 | static bool pressure_blocks_shrink(const char *mode) { |
930 | 5 | return mode && (strcmp(mode, "no_shrink") == 0 || strcmp(mode, "no_resize") == 0); |
931 | 5 | } |
932 | | |
933 | | /* Get the root variable name from a method call object expression */ |
934 | 7 | static const char *get_method_obj_varname(const Expr *obj) { |
935 | 7 | if (obj->tag == EXPR_IDENT) return obj->as.str_val; |
936 | 0 | if (obj->tag == EXPR_FIELD_ACCESS) return get_method_obj_varname(obj->as.field_access.object); |
937 | 0 | if (obj->tag == EXPR_INDEX) return get_method_obj_varname(obj->as.index.object); |
938 | 0 | return NULL; |
939 | 0 | } |
940 | | |
941 | | /* ── Function calling ── */ |
942 | | |
943 | | static EvalResult call_fn(Evaluator *ev, const FnDecl *decl, LatValue *args, size_t arg_count, |
944 | 1.26k | LatValue **writeback_out) { |
945 | | /* Determine required count (params without defaults and not variadic) */ |
946 | 1.26k | size_t required = 0; |
947 | 1.26k | bool has_variadic = false; |
948 | 1.99k | for (size_t i = 0; i < decl->param_count; i++) { |
949 | 735 | if (decl->params[i].is_variadic) { has_variadic = true; } |
950 | 731 | else if (!decl->params[i].default_value) required++; |
951 | 735 | } |
952 | 1.26k | size_t max_positional = has_variadic ? decl->param_count - 1 : decl->param_count; |
953 | 1.26k | if (arg_count < required || (!has_variadic && arg_count > max_positional)) { |
954 | 0 | char *err = NULL; |
955 | 0 | if (has_variadic) |
956 | 0 | (void)asprintf(&err, "function '%s' expects at least %zu arguments, got %zu", |
957 | 0 | decl->name, required, arg_count); |
958 | 0 | else if (required < max_positional) |
959 | 0 | (void)asprintf(&err, "function '%s' expects %zu to %zu arguments, got %zu", |
960 | 0 | decl->name, required, max_positional, arg_count); |
961 | 0 | else |
962 | 0 | (void)asprintf(&err, "function '%s' expects %zu arguments, got %zu", |
963 | 0 | decl->name, required, arg_count); |
964 | 0 | return eval_err(err); |
965 | 0 | } |
966 | | /* Phase constraint enforcement */ |
967 | 1.98k | for (size_t i = 0; i < decl->param_count && i < arg_count; i++) { |
968 | 731 | if (decl->params[i].is_variadic) break; |
969 | 729 | if (decl->params[i].ty.phase != PHASE_UNSPECIFIED && |
970 | 729 | !phase_compatible(args[i].phase, decl->params[i].ty.phase)) { |
971 | 3 | char *err = NULL; |
972 | 3 | (void)asprintf(&err, "function '%s' parameter '%s' requires %s argument, got %s", |
973 | 3 | decl->name, decl->params[i].name, |
974 | 3 | ast_phase_name(decl->params[i].ty.phase), |
975 | 3 | phase_tag_name(args[i].phase)); |
976 | 3 | return eval_err(err); |
977 | 3 | } |
978 | 729 | } |
979 | | /* Runtime type checking */ |
980 | 1.98k | for (size_t i = 0; i < decl->param_count && i < arg_count; i++) { |
981 | 728 | if (decl->params[i].is_variadic) break; |
982 | 726 | if (decl->params[i].ty.name && !type_matches_value(&args[i], &decl->params[i].ty)) { |
983 | 2 | char *err = NULL; |
984 | 2 | (void)asprintf(&err, "function '%s' parameter '%s' expects type %s, got %s", |
985 | 2 | decl->name, decl->params[i].name, |
986 | 2 | decl->params[i].ty.name, |
987 | 2 | value_type_display(&args[i])); |
988 | 2 | return eval_err(err); |
989 | 2 | } |
990 | 726 | } |
991 | 1.25k | stats_fn_call(&ev->stats); |
992 | 1.25k | ev_push_frame(ev, decl->name); |
993 | 1.25k | stats_scope_push(&ev->stats); |
994 | 1.25k | env_push_scope(ev->env); |
995 | 1.98k | for (size_t i = 0; i < decl->param_count; i++) { |
996 | 729 | if (decl->params[i].is_variadic) { |
997 | | /* Collect remaining args into an array */ |
998 | 4 | size_t rest_count = (arg_count > i) ? arg_count - i : 0; |
999 | 4 | LatValue *rest_elems = malloc(rest_count * sizeof(LatValue)); |
1000 | 10 | for (size_t j = 0; j < rest_count; j++) |
1001 | 6 | rest_elems[j] = args[i + j]; |
1002 | 4 | LatValue arr = value_array(rest_elems, rest_count); |
1003 | 4 | free(rest_elems); |
1004 | 4 | env_define(ev->env, decl->params[i].name, arr); |
1005 | 725 | } else if (i < arg_count) { |
1006 | 723 | env_define(ev->env, decl->params[i].name, args[i]); |
1007 | 723 | } else { |
1008 | | /* Use default value */ |
1009 | 2 | EvalResult def = eval_expr(ev, decl->params[i].default_value); |
1010 | 2 | if (!IS_OK(def)) { |
1011 | 0 | env_pop_scope(ev->env); |
1012 | 0 | stats_scope_pop(&ev->stats); |
1013 | 0 | return def; |
1014 | 0 | } |
1015 | 2 | env_define(ev->env, decl->params[i].name, def.value); |
1016 | 2 | } |
1017 | 729 | } |
1018 | | /* Evaluate require contracts */ |
1019 | 1.25k | if (ev->assertions_enabled && decl->contracts) { |
1020 | 11 | for (size_t i = 0; i < decl->contract_count; i++) { |
1021 | 7 | if (decl->contracts[i].is_ensure) continue; |
1022 | 5 | EvalResult cr = eval_expr(ev, decl->contracts[i].condition); |
1023 | 5 | if (!IS_OK(cr)) { env_pop_scope(ev->env); stats_scope_pop(&ev->stats); return cr; } |
1024 | 5 | bool truthy = (cr.value.type == VAL_BOOL && cr.value.as.bool_val); |
1025 | 5 | value_free(&cr.value); |
1026 | 5 | if (!truthy) { |
1027 | 2 | char *err = NULL; |
1028 | 2 | if (decl->contracts[i].message) |
1029 | 2 | (void)asprintf(&err, "require failed in '%s': %s", decl->name, decl->contracts[i].message); |
1030 | 0 | else |
1031 | 0 | (void)asprintf(&err, "require contract failed in '%s'", decl->name); |
1032 | 2 | env_pop_scope(ev->env); stats_scope_pop(&ev->stats); |
1033 | 2 | return eval_err(err); |
1034 | 2 | } |
1035 | 5 | } |
1036 | 6 | } |
1037 | | |
1038 | 1.25k | EvalResult result = eval_block_stmts(ev, decl->body, decl->body_count); |
1039 | | |
1040 | | /* Evaluate ensure contracts on the return value */ |
1041 | 1.25k | if (ev->assertions_enabled && decl->contracts && (IS_OK(result) || (IS_SIGNAL(result) && result.cf.tag == CF_RETURN))) { |
1042 | 4 | LatValue ret_val = IS_OK(result) ? result.value : result.cf.value; |
1043 | 8 | for (size_t i = 0; i < decl->contract_count; i++) { |
1044 | 5 | if (!decl->contracts[i].is_ensure) continue; |
1045 | | /* Ensure condition is a closure — call it with the return value */ |
1046 | 2 | EvalResult cc = eval_expr(ev, decl->contracts[i].condition); |
1047 | 2 | if (!IS_OK(cc)) { |
1048 | 0 | if (IS_OK(result)) value_free(&result.value); |
1049 | 0 | else value_free(&result.cf.value); |
1050 | 0 | env_pop_scope(ev->env); stats_scope_pop(&ev->stats); |
1051 | 0 | return cc; |
1052 | 0 | } |
1053 | 2 | if (cc.value.type == VAL_CLOSURE) { |
1054 | 2 | LatValue arg = value_deep_clone(&ret_val); |
1055 | 2 | LatValue call_args[1] = { arg }; |
1056 | 2 | EvalResult er = call_closure(ev, cc.value.as.closure.param_names, |
1057 | 2 | cc.value.as.closure.param_count, cc.value.as.closure.body, |
1058 | 2 | cc.value.as.closure.captured_env, call_args, 1, |
1059 | 2 | cc.value.as.closure.default_values, cc.value.as.closure.has_variadic); |
1060 | 2 | value_free(&arg); |
1061 | 2 | value_free(&cc.value); |
1062 | 2 | if (!IS_OK(er)) { |
1063 | 0 | if (IS_OK(result)) value_free(&result.value); |
1064 | 0 | else value_free(&result.cf.value); |
1065 | 0 | env_pop_scope(ev->env); stats_scope_pop(&ev->stats); |
1066 | 0 | return er; |
1067 | 0 | } |
1068 | 2 | bool truthy = (er.value.type == VAL_BOOL && er.value.as.bool_val); |
1069 | 2 | value_free(&er.value); |
1070 | 2 | if (!truthy) { |
1071 | 1 | if (IS_OK(result)) value_free(&result.value); |
1072 | 1 | else value_free(&result.cf.value); |
1073 | 1 | char *err = NULL; |
1074 | 1 | if (decl->contracts[i].message) |
1075 | 1 | (void)asprintf(&err, "ensure failed in '%s': %s", decl->name, decl->contracts[i].message); |
1076 | 0 | else |
1077 | 0 | (void)asprintf(&err, "ensure contract failed in '%s'", decl->name); |
1078 | 1 | env_pop_scope(ev->env); stats_scope_pop(&ev->stats); |
1079 | 1 | return eval_err(err); |
1080 | 1 | } |
1081 | 2 | } else { |
1082 | | /* ensure with a non-closure: evaluate as boolean directly */ |
1083 | 0 | bool truthy = (cc.value.type == VAL_BOOL && cc.value.as.bool_val); |
1084 | 0 | value_free(&cc.value); |
1085 | 0 | if (!truthy) { |
1086 | 0 | if (IS_OK(result)) value_free(&result.value); |
1087 | 0 | else value_free(&result.cf.value); |
1088 | 0 | char *err = NULL; |
1089 | 0 | if (decl->contracts[i].message) |
1090 | 0 | (void)asprintf(&err, "ensure failed in '%s': %s", decl->name, decl->contracts[i].message); |
1091 | 0 | else |
1092 | 0 | (void)asprintf(&err, "ensure contract failed in '%s'", decl->name); |
1093 | 0 | env_pop_scope(ev->env); stats_scope_pop(&ev->stats); |
1094 | 0 | return eval_err(err); |
1095 | 0 | } |
1096 | 0 | } |
1097 | 2 | } |
1098 | 4 | } |
1099 | | |
1100 | | /* Return type checking */ |
1101 | 1.25k | if (decl->return_type && (IS_OK(result) || (IS_SIGNAL(result) && result.cf.tag == CF_RETURN))) { |
1102 | 391 | LatValue *ret_val = IS_OK(result) ? &result.value : &result.cf.value; |
1103 | 391 | if (!type_matches_value(ret_val, decl->return_type)) { |
1104 | 2 | char *err = NULL; |
1105 | 2 | (void)asprintf(&err, "function '%s' return type expects %s, got %s", |
1106 | 2 | decl->name, decl->return_type->name, value_type_display(ret_val)); |
1107 | 2 | if (IS_OK(result)) value_free(&result.value); |
1108 | 2 | else value_free(&result.cf.value); |
1109 | 2 | env_pop_scope(ev->env); stats_scope_pop(&ev->stats); |
1110 | 2 | return eval_err(err); |
1111 | 2 | } |
1112 | 391 | } |
1113 | | |
1114 | | /* Before popping the scope, capture fluid parameter values for write-back */ |
1115 | 1.25k | if (writeback_out) { |
1116 | 412 | size_t wb_count = has_variadic ? decl->param_count - 1 : decl->param_count; |
1117 | 412 | if (wb_count > arg_count) wb_count = arg_count; |
1118 | 1.12k | for (size_t i = 0; i < wb_count; i++) { |
1119 | 713 | if (decl->params[i].ty.phase == PHASE_FLUID) { |
1120 | 4 | LatValue val; |
1121 | 4 | if (env_get(ev->env, decl->params[i].name, &val)) { |
1122 | 4 | writeback_out[i] = malloc(sizeof(LatValue)); |
1123 | 4 | *writeback_out[i] = val; |
1124 | 4 | } |
1125 | 4 | } |
1126 | 713 | } |
1127 | 412 | } |
1128 | | |
1129 | 1.25k | env_pop_scope(ev->env); |
1130 | 1.25k | stats_scope_pop(&ev->stats); |
1131 | | |
1132 | 1.25k | if (IS_ERR(result)) return result; /* leave frame on stack for trace */ |
1133 | 1.15k | ev_pop_frame(ev); |
1134 | | |
1135 | 1.15k | if (IS_SIGNAL(result) && result.cf.tag == CF_RETURN) { |
1136 | 396 | return eval_ok(result.cf.value); |
1137 | 396 | } |
1138 | 763 | return result; |
1139 | 1.15k | } |
1140 | | |
1141 | | static EvalResult call_native_closure(Evaluator *ev, void *native_fn, |
1142 | 64 | LatValue *args, size_t arg_count) { |
1143 | 64 | (void)ev; |
1144 | 64 | LatValue result = ext_call_native(native_fn, args, arg_count); |
1145 | | /* Check if the result is an error string */ |
1146 | 64 | if (result.type == VAL_STR && strncmp(result.as.str_val, "EVAL_ERROR:", 11) == 0) { |
1147 | 3 | char *msg = strdup(result.as.str_val + 11); |
1148 | 3 | value_free(&result); |
1149 | 3 | return eval_err(msg); |
1150 | 3 | } |
1151 | 61 | return eval_ok(result); |
1152 | 64 | } |
1153 | | |
1154 | | static EvalResult call_closure(Evaluator *ev, char **params, size_t param_count, |
1155 | | const Expr *body, Env *closure_env, LatValue *args, size_t arg_count, |
1156 | 821 | Expr **default_values, bool has_variadic) { |
1157 | | /* Determine required count */ |
1158 | 821 | size_t required = 0; |
1159 | 1.78k | for (size_t i = 0; i < param_count; i++) { |
1160 | 971 | if (has_variadic && i == param_count - 1) break; |
1161 | 963 | if (!default_values || !default_values[i]) required++; |
1162 | 963 | } |
1163 | 821 | size_t max_positional = has_variadic ? param_count - 1 : param_count; |
1164 | 821 | if (arg_count < required || (!has_variadic && arg_count > max_positional)) { |
1165 | 0 | char *err = NULL; |
1166 | 0 | if (has_variadic) |
1167 | 0 | (void)asprintf(&err, "closure expects at least %zu arguments, got %zu", required, arg_count); |
1168 | 0 | else if (required < max_positional) |
1169 | 0 | (void)asprintf(&err, "closure expects %zu to %zu arguments, got %zu", required, max_positional, arg_count); |
1170 | 0 | else |
1171 | 0 | (void)asprintf(&err, "closure expects %zu arguments, got %zu", param_count, arg_count); |
1172 | 0 | return eval_err(err); |
1173 | 0 | } |
1174 | 821 | stats_closure_call(&ev->stats); |
1175 | | |
1176 | | /* Swap environments — save caller env so GC can still mark it */ |
1177 | 821 | Env *saved = ev->env; |
1178 | 821 | lat_vec_push(&ev->saved_envs, &saved); |
1179 | 821 | ev->env = closure_env; |
1180 | 821 | stats_scope_push(&ev->stats); |
1181 | 821 | env_push_scope(ev->env); |
1182 | 1.79k | for (size_t i = 0; i < param_count; i++) { |
1183 | 971 | if (has_variadic && i == param_count - 1) { |
1184 | | /* Collect remaining args into an array */ |
1185 | 8 | size_t rest_count = (arg_count > i) ? arg_count - i : 0; |
1186 | 8 | LatValue *rest_elems = malloc(rest_count * sizeof(LatValue)); |
1187 | 19 | for (size_t j = 0; j < rest_count; j++) |
1188 | 11 | rest_elems[j] = args[i + j]; |
1189 | 8 | LatValue arr = value_array(rest_elems, rest_count); |
1190 | 8 | free(rest_elems); |
1191 | 8 | env_define(ev->env, params[i], arr); |
1192 | 963 | } else if (i < arg_count) { |
1193 | 947 | env_define(ev->env, params[i], args[i]); |
1194 | 947 | } else if (default_values && default_values[i]) { |
1195 | | /* Evaluate default in the closure environment */ |
1196 | 16 | EvalResult def = eval_expr(ev, default_values[i]); |
1197 | 16 | if (!IS_OK(def)) { |
1198 | 0 | env_pop_scope(ev->env); |
1199 | 0 | stats_scope_pop(&ev->stats); |
1200 | 0 | ev->env = saved; |
1201 | 0 | lat_vec_pop(&ev->saved_envs, NULL); |
1202 | 0 | return def; |
1203 | 0 | } |
1204 | 16 | env_define(ev->env, params[i], def.value); |
1205 | 16 | } |
1206 | 971 | } |
1207 | 821 | EvalResult result = eval_expr(ev, body); |
1208 | 821 | env_pop_scope(ev->env); |
1209 | 821 | stats_scope_pop(&ev->stats); |
1210 | 821 | ev->env = saved; |
1211 | 821 | lat_vec_pop(&ev->saved_envs, NULL); |
1212 | | |
1213 | 821 | if (IS_SIGNAL(result) && result.cf.tag == CF_RETURN) { |
1214 | 385 | return eval_ok(result.cf.value); |
1215 | 385 | } |
1216 | 436 | return result; |
1217 | 821 | } |
1218 | | |
1219 | | /* ── Value equality (for pattern matching) ── */ |
1220 | | |
1221 | 8 | static bool value_equal(const LatValue *a, const LatValue *b) { |
1222 | 8 | if (a->type != b->type) return false; |
1223 | 8 | switch (a->type) { |
1224 | 5 | case VAL_INT: return a->as.int_val == b->as.int_val; |
1225 | 0 | case VAL_FLOAT: return a->as.float_val == b->as.float_val; |
1226 | 0 | case VAL_BOOL: return a->as.bool_val == b->as.bool_val; |
1227 | 2 | case VAL_STR: return strcmp(a->as.str_val, b->as.str_val) == 0; |
1228 | 0 | case VAL_UNIT: return true; |
1229 | 1 | case VAL_NIL: return true; |
1230 | 0 | default: return false; |
1231 | 8 | } |
1232 | 8 | } |
1233 | | |
1234 | | /* ── Binary operations ── */ |
1235 | | |
1236 | 13.8k | static EvalResult eval_binop(BinOpKind op, LatValue *lv, LatValue *rv) { |
1237 | | /* Integer arithmetic */ |
1238 | 13.8k | if (lv->type == VAL_INT && rv->type == VAL_INT) { |
1239 | 13.1k | int64_t a = lv->as.int_val, b = rv->as.int_val; |
1240 | 13.1k | switch (op) { |
1241 | 6.86k | case BINOP_ADD: return eval_ok(value_int(a + b)); |
1242 | 43 | case BINOP_SUB: return eval_ok(value_int(a - b)); |
1243 | 2.13k | case BINOP_MUL: return eval_ok(value_int(a * b)); |
1244 | 11 | case BINOP_DIV: |
1245 | 11 | if (b == 0) return eval_err(strdup("division by zero")); |
1246 | 3 | return eval_ok(value_int(a / b)); |
1247 | 107 | case BINOP_MOD: |
1248 | 107 | if (b == 0) return eval_err(strdup("modulo by zero")); |
1249 | 107 | return eval_ok(value_int(a % b)); |
1250 | 214 | case BINOP_EQ: return eval_ok(value_bool(a == b)); |
1251 | 6 | case BINOP_NEQ: return eval_ok(value_bool(a != b)); |
1252 | 3.21k | case BINOP_LT: return eval_ok(value_bool(a < b)); |
1253 | 265 | case BINOP_GT: return eval_ok(value_bool(a > b)); |
1254 | 23 | case BINOP_LTEQ: return eval_ok(value_bool(a <= b)); |
1255 | 210 | case BINOP_GTEQ: return eval_ok(value_bool(a >= b)); |
1256 | 5 | case BINOP_BIT_AND: return eval_ok(value_int(a & b)); |
1257 | 4 | case BINOP_BIT_OR: return eval_ok(value_int(a | b)); |
1258 | 4 | case BINOP_BIT_XOR: return eval_ok(value_int(a ^ b)); |
1259 | 3 | case BINOP_LSHIFT: |
1260 | 3 | if (b < 0 || b > 63) return eval_err(strdup("shift amount out of range (0..63)")); |
1261 | 2 | return eval_ok(value_int(a << b)); |
1262 | 2 | case BINOP_RSHIFT: |
1263 | 2 | if (b < 0 || b > 63) return eval_err(strdup("shift amount out of range (0..63)")); |
1264 | 2 | return eval_ok(value_int(a >> b)); |
1265 | 0 | default: break; |
1266 | 13.1k | } |
1267 | 13.1k | } |
1268 | | /* Float arithmetic */ |
1269 | 786 | if (lv->type == VAL_FLOAT && rv->type == VAL_FLOAT) { |
1270 | 12 | double a = lv->as.float_val, b = rv->as.float_val; |
1271 | 12 | switch (op) { |
1272 | 2 | case BINOP_ADD: return eval_ok(value_float(a + b)); |
1273 | 1 | case BINOP_SUB: return eval_ok(value_float(a - b)); |
1274 | 1 | case BINOP_MUL: return eval_ok(value_float(a * b)); |
1275 | 2 | case BINOP_DIV: return eval_ok(value_float(a / b)); |
1276 | 0 | case BINOP_MOD: { |
1277 | 0 | double r = a - (int64_t)(a / b) * b; |
1278 | 0 | return eval_ok(value_float(r)); |
1279 | 0 | } |
1280 | 0 | case BINOP_EQ: return eval_ok(value_bool(a == b)); |
1281 | 0 | case BINOP_NEQ: return eval_ok(value_bool(a != b)); |
1282 | 2 | case BINOP_LT: return eval_ok(value_bool(a < b)); |
1283 | 3 | case BINOP_GT: return eval_ok(value_bool(a > b)); |
1284 | 0 | case BINOP_LTEQ: return eval_ok(value_bool(a <= b)); |
1285 | 1 | case BINOP_GTEQ: return eval_ok(value_bool(a >= b)); |
1286 | 0 | default: break; |
1287 | 12 | } |
1288 | 12 | } |
1289 | | /* Mixed int/float */ |
1290 | 774 | if ((lv->type == VAL_INT && rv->type == VAL_FLOAT) || |
1291 | 774 | (lv->type == VAL_FLOAT && rv->type == VAL_INT)) { |
1292 | 1 | double a = lv->type == VAL_FLOAT ? lv->as.float_val : (double)lv->as.int_val; |
1293 | 1 | double b = rv->type == VAL_FLOAT ? rv->as.float_val : (double)rv->as.int_val; |
1294 | 1 | switch (op) { |
1295 | 0 | case BINOP_ADD: return eval_ok(value_float(a + b)); |
1296 | 0 | case BINOP_SUB: return eval_ok(value_float(a - b)); |
1297 | 1 | case BINOP_MUL: return eval_ok(value_float(a * b)); |
1298 | 0 | case BINOP_DIV: return eval_ok(value_float(a / b)); |
1299 | 0 | default: break; |
1300 | 1 | } |
1301 | 1 | } |
1302 | | /* String concatenation */ |
1303 | 773 | if (lv->type == VAL_STR && rv->type == VAL_STR && op == BINOP_ADD) { |
1304 | 274 | size_t al = strlen(lv->as.str_val), bl = strlen(rv->as.str_val); |
1305 | 274 | char *buf = malloc(al + bl + 1); |
1306 | 274 | memcpy(buf, lv->as.str_val, al); |
1307 | 274 | memcpy(buf + al, rv->as.str_val, bl); |
1308 | 274 | buf[al + bl] = '\0'; |
1309 | 274 | return eval_ok(value_string_owned(buf)); |
1310 | 274 | } |
1311 | | /* String comparison */ |
1312 | 499 | if (lv->type == VAL_STR && rv->type == VAL_STR) { |
1313 | 370 | if (op == BINOP_EQ) return eval_ok(value_bool(strcmp(lv->as.str_val, rv->as.str_val) == 0)); |
1314 | 40 | if (op == BINOP_NEQ) return eval_ok(value_bool(strcmp(lv->as.str_val, rv->as.str_val) != 0)); |
1315 | 40 | } |
1316 | | /* Bool comparison */ |
1317 | 129 | if (lv->type == VAL_BOOL && rv->type == VAL_BOOL) { |
1318 | 71 | if (op == BINOP_EQ) return eval_ok(value_bool(lv->as.bool_val == rv->as.bool_val)); |
1319 | 67 | if (op == BINOP_NEQ) return eval_ok(value_bool(lv->as.bool_val != rv->as.bool_val)); |
1320 | 65 | if (op == BINOP_AND) return eval_ok(value_bool(lv->as.bool_val && rv->as.bool_val)); |
1321 | 2 | if (op == BINOP_OR) return eval_ok(value_bool(lv->as.bool_val || rv->as.bool_val)); |
1322 | 2 | } |
1323 | | /* Nil equality: nil == nil is true, nil == anything_else is false */ |
1324 | 58 | if ((op == BINOP_EQ || op == BINOP_NEQ) && |
1325 | 58 | (lv->type == VAL_NIL || rv->type == VAL_NIL)) { |
1326 | 48 | bool eq = (lv->type == VAL_NIL && rv->type == VAL_NIL); |
1327 | 48 | return eval_ok(value_bool(op == BINOP_EQ ? eq : !eq)); |
1328 | 48 | } |
1329 | | /* General equality using value_eq (enums, arrays, structs, etc.) */ |
1330 | 10 | if (lv->type == rv->type && (op == BINOP_EQ || op == BINOP_NEQ)) { |
1331 | 10 | bool eq = value_eq(lv, rv); |
1332 | 10 | return eval_ok(value_bool(op == BINOP_EQ ? eq : !eq)); |
1333 | 10 | } |
1334 | | |
1335 | 0 | char *err = NULL; |
1336 | 0 | (void)asprintf(&err, "unsupported binary operation on %s and %s", |
1337 | 0 | value_type_name(lv), value_type_name(rv)); |
1338 | 0 | return eval_err(err); |
1339 | 10 | } |
1340 | | |
1341 | 49 | static EvalResult eval_unaryop(UnaryOpKind op, LatValue *val) { |
1342 | 49 | if (op == UNOP_NEG && val->type == VAL_INT) return eval_ok(value_int(-val->as.int_val)); |
1343 | 34 | if (op == UNOP_NEG && val->type == VAL_FLOAT) return eval_ok(value_float(-val->as.float_val)); |
1344 | 33 | if (op == UNOP_NOT && val->type == VAL_BOOL) return eval_ok(value_bool(!val->as.bool_val)); |
1345 | 3 | if (op == UNOP_BIT_NOT && val->type == VAL_INT) return eval_ok(value_int(~val->as.int_val)); |
1346 | 0 | char *err = NULL; |
1347 | 0 | (void)asprintf(&err, "unsupported unary operation on %s", value_type_name(val)); |
1348 | 0 | return eval_err(err); |
1349 | 3 | } |
1350 | | |
1351 | | /* ── Concurrency infrastructure ── */ |
1352 | | |
1353 | | #ifndef __EMSCRIPTEN__ |
1354 | | typedef struct { |
1355 | | Stmt **stmts; |
1356 | | size_t stmt_count; |
1357 | | Evaluator *child_ev; |
1358 | | char *error; /* NULL on success */ |
1359 | | pthread_t thread; |
1360 | | } SpawnTask; |
1361 | | |
1362 | 3 | static Evaluator *create_child_evaluator(Evaluator *parent) { |
1363 | 3 | Evaluator *child = calloc(1, sizeof(Evaluator)); |
1364 | 3 | child->env = env_clone(parent->env); |
1365 | 3 | child->mode = parent->mode; |
1366 | | /* Share AST pointers (borrowed, immutable after parse) */ |
1367 | 3 | child->struct_defs = lat_map_new(sizeof(StructDecl *)); |
1368 | 51 | for (size_t i = 0; i < parent->struct_defs.cap; i++) { |
1369 | 48 | if (parent->struct_defs.entries[i].state == MAP_OCCUPIED) { |
1370 | 0 | lat_map_set(&child->struct_defs, |
1371 | 0 | parent->struct_defs.entries[i].key, |
1372 | 0 | parent->struct_defs.entries[i].value); |
1373 | 0 | } |
1374 | 48 | } |
1375 | 3 | child->fn_defs = lat_map_new(sizeof(FnDecl *)); |
1376 | 51 | for (size_t i = 0; i < parent->fn_defs.cap; i++) { |
1377 | 48 | if (parent->fn_defs.entries[i].state == MAP_OCCUPIED) { |
1378 | 8 | lat_map_set(&child->fn_defs, |
1379 | 8 | parent->fn_defs.entries[i].key, |
1380 | 8 | parent->fn_defs.entries[i].value); |
1381 | 8 | } |
1382 | 48 | } |
1383 | 3 | stats_init(&child->stats); |
1384 | 3 | child->heap = dual_heap_new(); |
1385 | 3 | child->gc_roots = lat_vec_new(sizeof(LatValue *)); |
1386 | 3 | child->saved_envs = lat_vec_new(sizeof(Env *)); |
1387 | 3 | child->gc_stress = parent->gc_stress; |
1388 | 3 | child->no_regions = parent->no_regions; |
1389 | 3 | child->required_files = lat_map_new(sizeof(bool)); |
1390 | 3 | child->script_dir = parent->script_dir ? strdup(parent->script_dir) : NULL; |
1391 | 3 | return child; |
1392 | 3 | } |
1393 | | |
1394 | 3 | static void free_child_evaluator(Evaluator *child) { |
1395 | 3 | if (!child) return; |
1396 | 3 | env_free(child->env); |
1397 | 3 | lat_map_free(&child->struct_defs); |
1398 | 3 | lat_map_free(&child->fn_defs); |
1399 | 3 | lat_map_free(&child->required_files); |
1400 | 3 | free(child->script_dir); |
1401 | 3 | value_set_heap(NULL); |
1402 | 3 | dual_heap_free(child->heap); |
1403 | 3 | lat_vec_free(&child->gc_roots); |
1404 | 3 | lat_vec_free(&child->saved_envs); |
1405 | 3 | free(child); |
1406 | 3 | } |
1407 | | |
1408 | 3 | static void *spawn_thread_fn(void *arg) { |
1409 | 3 | SpawnTask *task = (SpawnTask *)arg; |
1410 | 3 | Evaluator *child = task->child_ev; |
1411 | | |
1412 | | /* Set thread-local heap for this child evaluator */ |
1413 | 3 | value_set_heap(child->heap); |
1414 | 3 | value_set_arena(NULL); |
1415 | | |
1416 | 3 | EvalResult result = eval_block_stmts(child, task->stmts, task->stmt_count); |
1417 | | |
1418 | 3 | if (IS_ERR(result)) { |
1419 | 1 | task->error = result.error; /* transfer ownership */ |
1420 | 2 | } else if (IS_SIGNAL(result)) { |
1421 | 0 | switch (result.cf.tag) { |
1422 | 0 | case CF_RETURN: |
1423 | 0 | task->error = strdup("cannot use 'return' inside spawn"); |
1424 | 0 | value_free(&result.cf.value); |
1425 | 0 | break; |
1426 | 0 | case CF_BREAK: |
1427 | 0 | task->error = strdup("cannot use 'break' inside spawn"); |
1428 | 0 | break; |
1429 | 0 | case CF_CONTINUE: |
1430 | 0 | task->error = strdup("cannot use 'continue' inside spawn"); |
1431 | 0 | break; |
1432 | 0 | default: |
1433 | 0 | break; |
1434 | 0 | } |
1435 | 2 | } else { |
1436 | 2 | value_free(&result.value); |
1437 | 2 | } |
1438 | | |
1439 | 3 | return NULL; |
1440 | 3 | } |
1441 | | #endif /* __EMSCRIPTEN__ */ |
1442 | | |
1443 | | /* ── Expression evaluation ── */ |
1444 | | |
1445 | 68.3k | static EvalResult eval_expr_inner(Evaluator *ev, const Expr *expr) { |
1446 | 68.3k | switch (expr->tag) { |
1447 | 13.6k | case EXPR_INT_LIT: return eval_ok(value_int(expr->as.int_val)); |
1448 | 83 | case EXPR_FLOAT_LIT: return eval_ok(value_float(expr->as.float_val)); |
1449 | 3.41k | case EXPR_STRING_LIT: return eval_ok(value_string(expr->as.str_val)); |
1450 | 327 | case EXPR_BOOL_LIT: return eval_ok(value_bool(expr->as.bool_val)); |
1451 | 85 | case EXPR_NIL_LIT: return eval_ok(value_nil()); |
1452 | | |
1453 | 22.8k | case EXPR_IDENT: { |
1454 | 22.8k | LatValue val; |
1455 | 22.8k | if (!env_get(ev->env, expr->as.str_val, &val)) { |
1456 | 4 | char *err = NULL; |
1457 | 4 | (void)asprintf(&err, "undefined variable '%s'", expr->as.str_val); |
1458 | 4 | return eval_err(err); |
1459 | 4 | } |
1460 | 22.8k | return eval_ok(val); |
1461 | 22.8k | } |
1462 | | |
1463 | 13.8k | case EXPR_BINOP: { |
1464 | | /* Short-circuit: ?? (nil coalescing) */ |
1465 | 13.8k | if (expr->as.binop.op == BINOP_NIL_COALESCE) { |
1466 | 6 | EvalResult lr = eval_expr(ev, expr->as.binop.left); |
1467 | 6 | if (!IS_OK(lr)) return lr; |
1468 | 6 | if (lr.value.type != VAL_NIL) return lr; |
1469 | 5 | value_free(&lr.value); |
1470 | 5 | return eval_expr(ev, expr->as.binop.right); |
1471 | 6 | } |
1472 | 13.8k | EvalResult lr = eval_expr(ev, expr->as.binop.left); |
1473 | 13.8k | if (!IS_OK(lr)) return lr; |
1474 | 13.8k | GC_PUSH(ev, &lr.value); |
1475 | 13.8k | EvalResult rr = eval_expr(ev, expr->as.binop.right); |
1476 | 13.8k | GC_POP(ev); |
1477 | 13.8k | if (!IS_OK(rr)) { value_free(&lr.value); return rr; } |
1478 | 13.8k | EvalResult res = eval_binop(expr->as.binop.op, &lr.value, &rr.value); |
1479 | 13.8k | value_free(&lr.value); |
1480 | 13.8k | value_free(&rr.value); |
1481 | 13.8k | return res; |
1482 | 13.8k | } |
1483 | | |
1484 | 49 | case EXPR_UNARYOP: { |
1485 | 49 | EvalResult vr = eval_expr(ev, expr->as.unaryop.operand); |
1486 | 49 | if (!IS_OK(vr)) return vr; |
1487 | 49 | EvalResult res = eval_unaryop(expr->as.unaryop.op, &vr.value); |
1488 | 49 | value_free(&vr.value); |
1489 | 49 | return res; |
1490 | 49 | } |
1491 | | |
1492 | 2.42k | case EXPR_CALL: { |
1493 | | /* ── track(x) / history(x) / phases(x) / rewind(x, n): convert ident to string ── */ |
1494 | 2.42k | if (expr->as.call.func->tag == EXPR_IDENT) { |
1495 | 2.42k | const char *cfn = expr->as.call.func->as.str_val; |
1496 | 2.42k | bool is_1arg = (strcmp(cfn, "track") == 0 || strcmp(cfn, "history") == 0 || |
1497 | 2.42k | strcmp(cfn, "phases") == 0); |
1498 | 2.42k | bool is_rewind = strcmp(cfn, "rewind") == 0; |
1499 | 2.42k | if ((is_1arg && expr->as.call.arg_count == 1 && |
1500 | 2.42k | expr->as.call.args[0]->tag == EXPR_IDENT) || |
1501 | 2.42k | (is_rewind && expr->as.call.arg_count == 2 && |
1502 | 2.41k | expr->as.call.args[0]->tag == EXPR_IDENT)) { |
1503 | | /* Temporarily swap first arg to a string literal with the variable name */ |
1504 | 10 | Expr *orig = expr->as.call.args[0]; |
1505 | 10 | Expr tmp_str; |
1506 | 10 | tmp_str.tag = EXPR_STRING_LIT; |
1507 | 10 | tmp_str.as.str_val = orig->as.str_val; |
1508 | 10 | tmp_str.line = orig->line; |
1509 | 10 | expr->as.call.args[0] = &tmp_str; |
1510 | 10 | EvalResult r = eval_expr(ev, expr); |
1511 | 10 | expr->as.call.args[0] = orig; /* restore */ |
1512 | 10 | return r; |
1513 | 10 | } |
1514 | 2.42k | } |
1515 | | /* ── react() / unreact(): special handling before arg evaluation ── */ |
1516 | 2.41k | if (expr->as.call.func->tag == EXPR_IDENT && |
1517 | 2.41k | strcmp(expr->as.call.func->as.str_val, "react") == 0) { |
1518 | 9 | size_t argc = expr->as.call.arg_count; |
1519 | 9 | if (argc != 2) { |
1520 | 0 | return eval_err(strdup("react() requires exactly 2 arguments (variable, callback)")); |
1521 | 0 | } |
1522 | 9 | if (expr->as.call.args[0]->tag != EXPR_IDENT) { |
1523 | 0 | return eval_err(strdup("react() first argument must be a variable name")); |
1524 | 0 | } |
1525 | 9 | const char *var_name = expr->as.call.args[0]->as.str_val; |
1526 | | /* Verify variable exists */ |
1527 | 9 | LatValue tmp; |
1528 | 9 | if (!env_get(ev->env, var_name, &tmp)) { |
1529 | 0 | char *err = NULL; |
1530 | 0 | (void)asprintf(&err, "cannot react to undefined variable '%s'", var_name); |
1531 | 0 | return eval_err(err); |
1532 | 0 | } |
1533 | 9 | value_free(&tmp); |
1534 | | /* Evaluate the callback */ |
1535 | 9 | EvalResult cbr = eval_expr(ev, expr->as.call.args[1]); |
1536 | 9 | if (!IS_OK(cbr)) return cbr; |
1537 | 9 | if (cbr.value.type != VAL_CLOSURE) { |
1538 | 0 | value_free(&cbr.value); |
1539 | 0 | return eval_err(strdup("react() second argument must be a closure")); |
1540 | 0 | } |
1541 | | /* Find or create ReactionEntry for var_name */ |
1542 | 9 | ReactionEntry *re = NULL; |
1543 | 9 | for (size_t i = 0; i < ev->reaction_count; i++) { |
1544 | 1 | if (strcmp(ev->reactions[i].var_name, var_name) == 0) { |
1545 | 1 | re = &ev->reactions[i]; |
1546 | 1 | break; |
1547 | 1 | } |
1548 | 1 | } |
1549 | 9 | if (!re) { |
1550 | 8 | if (ev->reaction_count >= ev->reaction_cap) { |
1551 | 8 | ev->reaction_cap = ev->reaction_cap ? ev->reaction_cap * 2 : 4; |
1552 | 8 | ev->reactions = realloc(ev->reactions, ev->reaction_cap * sizeof(ReactionEntry)); |
1553 | 8 | } |
1554 | 8 | re = &ev->reactions[ev->reaction_count++]; |
1555 | 8 | re->var_name = strdup(var_name); |
1556 | 8 | re->callbacks = NULL; |
1557 | 8 | re->cb_count = 0; |
1558 | 8 | re->cb_cap = 0; |
1559 | 8 | } |
1560 | | /* Add the callback */ |
1561 | 9 | if (re->cb_count >= re->cb_cap) { |
1562 | 8 | re->cb_cap = re->cb_cap ? re->cb_cap * 2 : 4; |
1563 | 8 | re->callbacks = realloc(re->callbacks, re->cb_cap * sizeof(LatValue)); |
1564 | 8 | } |
1565 | 9 | re->callbacks[re->cb_count++] = value_deep_clone(&cbr.value); |
1566 | 9 | value_free(&cbr.value); |
1567 | 9 | return eval_ok(value_unit()); |
1568 | 9 | } |
1569 | 2.40k | if (expr->as.call.func->tag == EXPR_IDENT && |
1570 | 2.40k | strcmp(expr->as.call.func->as.str_val, "unreact") == 0) { |
1571 | 1 | size_t argc = expr->as.call.arg_count; |
1572 | 1 | if (argc != 1) { |
1573 | 0 | return eval_err(strdup("unreact() requires exactly 1 argument (variable)")); |
1574 | 0 | } |
1575 | 1 | if (expr->as.call.args[0]->tag != EXPR_IDENT) { |
1576 | 0 | return eval_err(strdup("unreact() argument must be a variable name")); |
1577 | 0 | } |
1578 | 1 | const char *var_name = expr->as.call.args[0]->as.str_val; |
1579 | | /* Find and remove ReactionEntry */ |
1580 | 1 | for (size_t i = 0; i < ev->reaction_count; i++) { |
1581 | 1 | if (strcmp(ev->reactions[i].var_name, var_name) == 0) { |
1582 | 1 | free(ev->reactions[i].var_name); |
1583 | 2 | for (size_t j = 0; j < ev->reactions[i].cb_count; j++) |
1584 | 1 | value_free(&ev->reactions[i].callbacks[j]); |
1585 | 1 | free(ev->reactions[i].callbacks); |
1586 | 1 | ev->reactions[i] = ev->reactions[--ev->reaction_count]; |
1587 | 1 | break; |
1588 | 1 | } |
1589 | 1 | } |
1590 | 1 | return eval_ok(value_unit()); |
1591 | 1 | } |
1592 | | /* ── bond() / unbond(): special handling before arg evaluation ── */ |
1593 | 2.40k | if (expr->as.call.func->tag == EXPR_IDENT && |
1594 | 2.40k | (strcmp(expr->as.call.func->as.str_val, "bond") == 0 || |
1595 | 2.40k | strcmp(expr->as.call.func->as.str_val, "unbond") == 0)) { |
1596 | 16 | bool is_bond = strcmp(expr->as.call.func->as.str_val, "bond") == 0; |
1597 | 16 | size_t argc = expr->as.call.arg_count; |
1598 | 16 | if (argc < 2) { |
1599 | 0 | return eval_err(strdup(is_bond |
1600 | 0 | ? "bond() requires at least 2 arguments (target, dep[, strategy])" |
1601 | 0 | : "unbond() requires at least 2 arguments (target, ...deps)")); |
1602 | 0 | } |
1603 | | /* First 2 arguments must be identifiers; optional 3rd can be string (strategy) */ |
1604 | 16 | if (expr->as.call.args[0]->tag != EXPR_IDENT || |
1605 | 16 | expr->as.call.args[1]->tag != EXPR_IDENT) { |
1606 | 1 | return eval_err(strdup(is_bond |
1607 | 1 | ? "bond() requires variable names for first two arguments" |
1608 | 1 | : "unbond() requires variable names, not expressions")); |
1609 | 1 | } |
1610 | 15 | if (!is_bond) { |
1611 | 1 | for (size_t i = 2; i < argc; i++) { |
1612 | 0 | if (expr->as.call.args[i]->tag != EXPR_IDENT) { |
1613 | 0 | return eval_err(strdup("unbond() requires variable names, not expressions")); |
1614 | 0 | } |
1615 | 0 | } |
1616 | 1 | } |
1617 | 15 | const char *target = expr->as.call.args[0]->as.str_val; |
1618 | 15 | if (is_bond) { |
1619 | | /* Determine strategy: if last arg is a string literal, use as strategy. |
1620 | | * Otherwise all args after target are deps with default "mirror" strategy. |
1621 | | * bond(target, dep) — mirror |
1622 | | * bond(target, dep, "inverse") — explicit strategy |
1623 | | * bond(target, dep1, dep2, ...) — multiple deps, all mirror */ |
1624 | 14 | char *strategy = NULL; |
1625 | 14 | size_t dep_end = argc; /* how many args are deps (excluding target at 0) */ |
1626 | 14 | if (argc >= 3 && expr->as.call.args[argc - 1]->tag == EXPR_STRING_LIT) { |
1627 | | /* Last arg is a string literal — treat as strategy */ |
1628 | 3 | const char *sval = expr->as.call.args[argc - 1]->as.str_val; |
1629 | 3 | if (strcmp(sval, "mirror") == 0 || strcmp(sval, "inverse") == 0 || |
1630 | 3 | strcmp(sval, "gate") == 0) { |
1631 | 3 | strategy = strdup(sval); |
1632 | 3 | dep_end = argc - 1; |
1633 | 3 | } |
1634 | 3 | } |
1635 | 14 | if (!strategy) strategy = strdup("mirror"); |
1636 | | /* All args from 1..dep_end-1 must be idents (dep vars) */ |
1637 | 30 | for (size_t i = 1; i < dep_end; i++) { |
1638 | 16 | if (expr->as.call.args[i]->tag != EXPR_IDENT) { |
1639 | 0 | free(strategy); |
1640 | 0 | return eval_err(strdup("bond() dependency arguments must be variable names")); |
1641 | 0 | } |
1642 | 16 | } |
1643 | | /* Verify all variables exist and are not frozen */ |
1644 | 41 | for (size_t i = 0; i < dep_end; i++) { |
1645 | 29 | const char *vname = expr->as.call.args[i]->as.str_val; |
1646 | 29 | LatValue tmp; |
1647 | 29 | if (!env_get(ev->env, vname, &tmp)) { |
1648 | 1 | char *err = NULL; |
1649 | 1 | (void)asprintf(&err, "cannot bond undefined variable '%s'", vname); |
1650 | 1 | free(strategy); |
1651 | 1 | return eval_err(err); |
1652 | 1 | } |
1653 | 28 | if (tmp.phase == VTAG_CRYSTAL) { |
1654 | 1 | value_free(&tmp); |
1655 | 1 | char *err = NULL; |
1656 | 1 | (void)asprintf(&err, "cannot bond already-frozen variable '%s'", vname); |
1657 | 1 | free(strategy); |
1658 | 1 | return eval_err(err); |
1659 | 1 | } |
1660 | 27 | value_free(&tmp); |
1661 | 27 | } |
1662 | | /* Register bonds for each dep */ |
1663 | 12 | BondEntry *be = NULL; |
1664 | 13 | for (size_t i = 0; i < ev->bond_count; i++) { |
1665 | 1 | if (strcmp(ev->bonds[i].target, target) == 0) { be = &ev->bonds[i]; break; } |
1666 | 1 | } |
1667 | 12 | if (!be) { |
1668 | 12 | if (ev->bond_count >= ev->bond_cap) { |
1669 | 11 | ev->bond_cap = ev->bond_cap ? ev->bond_cap * 2 : 4; |
1670 | 11 | ev->bonds = realloc(ev->bonds, ev->bond_cap * sizeof(BondEntry)); |
1671 | 11 | } |
1672 | 12 | be = &ev->bonds[ev->bond_count++]; |
1673 | 12 | be->target = strdup(target); |
1674 | 12 | be->deps = NULL; |
1675 | 12 | be->dep_strategies = NULL; |
1676 | 12 | be->dep_count = 0; |
1677 | 12 | be->dep_cap = 0; |
1678 | 12 | } |
1679 | 26 | for (size_t di = 1; di < dep_end; di++) { |
1680 | 14 | const char *dep = expr->as.call.args[di]->as.str_val; |
1681 | 14 | if (be->dep_count >= be->dep_cap) { |
1682 | 12 | be->dep_cap = be->dep_cap ? be->dep_cap * 2 : 4; |
1683 | 12 | be->deps = realloc(be->deps, be->dep_cap * sizeof(char *)); |
1684 | 12 | be->dep_strategies = realloc(be->dep_strategies, be->dep_cap * sizeof(char *)); |
1685 | 12 | } |
1686 | 14 | be->deps[be->dep_count] = strdup(dep); |
1687 | 14 | be->dep_strategies[be->dep_count] = strdup(strategy); |
1688 | 14 | be->dep_count++; |
1689 | 14 | } |
1690 | 12 | free(strategy); |
1691 | 12 | } else { |
1692 | | /* unbond: remove deps from target's bond set */ |
1693 | 1 | for (size_t i = 0; i < ev->bond_count; i++) { |
1694 | 1 | if (strcmp(ev->bonds[i].target, target) == 0) { |
1695 | 2 | for (size_t j = 1; j < argc; j++) { |
1696 | 1 | const char *dep = expr->as.call.args[j]->as.str_val; |
1697 | 1 | for (size_t k = 0; k < ev->bonds[i].dep_count; k++) { |
1698 | 1 | if (strcmp(ev->bonds[i].deps[k], dep) == 0) { |
1699 | 1 | free(ev->bonds[i].deps[k]); |
1700 | 1 | if (ev->bonds[i].dep_strategies) free(ev->bonds[i].dep_strategies[k]); |
1701 | 1 | ev->bonds[i].deps[k] = ev->bonds[i].deps[ev->bonds[i].dep_count - 1]; |
1702 | 1 | if (ev->bonds[i].dep_strategies) |
1703 | 1 | ev->bonds[i].dep_strategies[k] = ev->bonds[i].dep_strategies[ev->bonds[i].dep_count - 1]; |
1704 | 1 | ev->bonds[i].dep_count--; |
1705 | 1 | break; |
1706 | 1 | } |
1707 | 1 | } |
1708 | 1 | } |
1709 | | /* Remove entry if empty */ |
1710 | 1 | if (ev->bonds[i].dep_count == 0) { |
1711 | 1 | free(ev->bonds[i].target); |
1712 | 1 | free(ev->bonds[i].deps); |
1713 | 1 | free(ev->bonds[i].dep_strategies); |
1714 | 1 | ev->bonds[i] = ev->bonds[--ev->bond_count]; |
1715 | 1 | } |
1716 | 1 | break; |
1717 | 1 | } |
1718 | 1 | } |
1719 | 1 | } |
1720 | 13 | return eval_ok(value_unit()); |
1721 | 15 | } |
1722 | | |
1723 | | /* ── seed() / unseed(): special handling before arg evaluation ── */ |
1724 | 2.39k | if (expr->as.call.func->tag == EXPR_IDENT && |
1725 | 2.39k | strcmp(expr->as.call.func->as.str_val, "seed") == 0) { |
1726 | 4 | size_t argc = expr->as.call.arg_count; |
1727 | 4 | if (argc != 2) return eval_err(strdup("seed() requires exactly 2 arguments (variable, contract)")); |
1728 | 4 | if (expr->as.call.args[0]->tag != EXPR_IDENT) |
1729 | 0 | return eval_err(strdup("seed() first argument must be a variable name")); |
1730 | 4 | const char *var_name = expr->as.call.args[0]->as.str_val; |
1731 | 4 | LatValue tmp; |
1732 | 4 | if (!env_get(ev->env, var_name, &tmp)) { |
1733 | 0 | char *err = NULL; |
1734 | 0 | (void)asprintf(&err, "seed(): undefined variable '%s'", var_name); |
1735 | 0 | return eval_err(err); |
1736 | 0 | } |
1737 | 4 | value_free(&tmp); |
1738 | 4 | EvalResult cbr = eval_expr(ev, expr->as.call.args[1]); |
1739 | 4 | if (!IS_OK(cbr)) return cbr; |
1740 | 4 | if (cbr.value.type != VAL_CLOSURE) { |
1741 | 0 | value_free(&cbr.value); |
1742 | 0 | return eval_err(strdup("seed() second argument must be a closure")); |
1743 | 0 | } |
1744 | | /* Store seed entry */ |
1745 | 4 | if (ev->seed_count >= ev->seed_cap) { |
1746 | 4 | ev->seed_cap = ev->seed_cap ? ev->seed_cap * 2 : 4; |
1747 | 4 | ev->seeds = realloc(ev->seeds, ev->seed_cap * sizeof(SeedEntry)); |
1748 | 4 | } |
1749 | 4 | ev->seeds[ev->seed_count].var_name = strdup(var_name); |
1750 | 4 | ev->seeds[ev->seed_count].contract = value_deep_clone(&cbr.value); |
1751 | 4 | ev->seed_count++; |
1752 | 4 | value_free(&cbr.value); |
1753 | 4 | return eval_ok(value_unit()); |
1754 | 4 | } |
1755 | 2.38k | if (expr->as.call.func->tag == EXPR_IDENT && |
1756 | 2.38k | strcmp(expr->as.call.func->as.str_val, "unseed") == 0) { |
1757 | 1 | size_t argc = expr->as.call.arg_count; |
1758 | 1 | if (argc != 1) return eval_err(strdup("unseed() requires exactly 1 argument (variable)")); |
1759 | 1 | if (expr->as.call.args[0]->tag != EXPR_IDENT) |
1760 | 0 | return eval_err(strdup("unseed() argument must be a variable name")); |
1761 | 1 | const char *var_name = expr->as.call.args[0]->as.str_val; |
1762 | 1 | for (size_t i = 0; i < ev->seed_count; i++) { |
1763 | 1 | if (strcmp(ev->seeds[i].var_name, var_name) == 0) { |
1764 | 1 | free(ev->seeds[i].var_name); |
1765 | 1 | value_free(&ev->seeds[i].contract); |
1766 | 1 | ev->seeds[i] = ev->seeds[--ev->seed_count]; |
1767 | 1 | break; |
1768 | 1 | } |
1769 | 1 | } |
1770 | 1 | return eval_ok(value_unit()); |
1771 | 1 | } |
1772 | | /* ── pressurize() / depressurize(): special handling before arg evaluation ── */ |
1773 | 2.38k | if (expr->as.call.func->tag == EXPR_IDENT && |
1774 | 2.38k | strcmp(expr->as.call.func->as.str_val, "pressurize") == 0) { |
1775 | 6 | size_t argc = expr->as.call.arg_count; |
1776 | 6 | if (argc != 2) return eval_err(strdup("pressurize() requires 2 arguments (variable, mode)")); |
1777 | 6 | if (expr->as.call.args[0]->tag != EXPR_IDENT) |
1778 | 0 | return eval_err(strdup("pressurize() first argument must be a variable name")); |
1779 | 6 | const char *var_name = expr->as.call.args[0]->as.str_val; |
1780 | 6 | LatValue tmp; |
1781 | 6 | if (!env_get(ev->env, var_name, &tmp)) { |
1782 | 0 | char *err = NULL; |
1783 | 0 | (void)asprintf(&err, "pressurize(): undefined variable '%s'", var_name); |
1784 | 0 | return eval_err(err); |
1785 | 0 | } |
1786 | 6 | value_free(&tmp); |
1787 | 6 | EvalResult mr = eval_expr(ev, expr->as.call.args[1]); |
1788 | 6 | if (!IS_OK(mr)) return mr; |
1789 | 6 | if (mr.value.type != VAL_STR) { |
1790 | 0 | value_free(&mr.value); |
1791 | 0 | return eval_err(strdup("pressurize() mode must be a string")); |
1792 | 0 | } |
1793 | 6 | const char *mode = mr.value.as.str_val; |
1794 | 6 | if (strcmp(mode, "no_grow") != 0 && strcmp(mode, "no_shrink") != 0 && |
1795 | 6 | strcmp(mode, "no_resize") != 0 && strcmp(mode, "read_heavy") != 0) { |
1796 | 0 | char *err = NULL; |
1797 | 0 | (void)asprintf(&err, "pressurize() unknown mode '%s'", mode); |
1798 | 0 | value_free(&mr.value); |
1799 | 0 | return eval_err(err); |
1800 | 0 | } |
1801 | | /* Find or create PressureEntry */ |
1802 | 6 | PressureEntry *pe = NULL; |
1803 | 6 | for (size_t i = 0; i < ev->pressure_count; i++) { |
1804 | 0 | if (strcmp(ev->pressures[i].var_name, var_name) == 0) { pe = &ev->pressures[i]; break; } |
1805 | 0 | } |
1806 | 6 | if (pe) { |
1807 | 0 | free(pe->mode); |
1808 | 0 | pe->mode = strdup(mode); |
1809 | 6 | } else { |
1810 | 6 | if (ev->pressure_count >= ev->pressure_cap) { |
1811 | 6 | ev->pressure_cap = ev->pressure_cap ? ev->pressure_cap * 2 : 4; |
1812 | 6 | ev->pressures = realloc(ev->pressures, ev->pressure_cap * sizeof(PressureEntry)); |
1813 | 6 | } |
1814 | 6 | pe = &ev->pressures[ev->pressure_count++]; |
1815 | 6 | pe->var_name = strdup(var_name); |
1816 | 6 | pe->mode = strdup(mode); |
1817 | 6 | } |
1818 | 6 | value_free(&mr.value); |
1819 | 6 | return eval_ok(value_unit()); |
1820 | 6 | } |
1821 | 2.37k | if (expr->as.call.func->tag == EXPR_IDENT && |
1822 | 2.37k | strcmp(expr->as.call.func->as.str_val, "depressurize") == 0) { |
1823 | 1 | size_t argc = expr->as.call.arg_count; |
1824 | 1 | if (argc != 1) return eval_err(strdup("depressurize() requires 1 argument (variable)")); |
1825 | 1 | if (expr->as.call.args[0]->tag != EXPR_IDENT) |
1826 | 0 | return eval_err(strdup("depressurize() argument must be a variable name")); |
1827 | 1 | const char *var_name = expr->as.call.args[0]->as.str_val; |
1828 | 1 | for (size_t i = 0; i < ev->pressure_count; i++) { |
1829 | 1 | if (strcmp(ev->pressures[i].var_name, var_name) == 0) { |
1830 | 1 | free(ev->pressures[i].var_name); |
1831 | 1 | free(ev->pressures[i].mode); |
1832 | 1 | ev->pressures[i] = ev->pressures[--ev->pressure_count]; |
1833 | 1 | break; |
1834 | 1 | } |
1835 | 1 | } |
1836 | 1 | return eval_ok(value_unit()); |
1837 | 1 | } |
1838 | | |
1839 | | /* Evaluate arguments */ |
1840 | 2.37k | size_t argc = expr->as.call.arg_count; |
1841 | 2.37k | LatValue *args = malloc(argc * sizeof(LatValue)); |
1842 | 4.80k | for (size_t i = 0; i < argc; i++) { |
1843 | 2.42k | EvalResult ar = eval_expr(ev, expr->as.call.args[i]); |
1844 | 2.42k | if (!IS_OK(ar)) { |
1845 | 0 | GC_POP_N(ev, i); |
1846 | 0 | for (size_t j = 0; j < i; j++) value_free(&args[j]); |
1847 | 0 | free(args); |
1848 | 0 | return ar; |
1849 | 0 | } |
1850 | 2.42k | args[i] = ar.value; |
1851 | 2.42k | GC_PUSH(ev, &args[i]); |
1852 | 2.42k | } |
1853 | 2.37k | GC_POP_N(ev, argc); |
1854 | | /* Check for named function or built-in */ |
1855 | 2.37k | if (expr->as.call.func->tag == EXPR_IDENT) { |
1856 | 2.37k | const char *fn_name = expr->as.call.func->as.str_val; |
1857 | | |
1858 | | /* ── Built-in functions ── */ |
1859 | | |
1860 | | /// @builtin input(prompt?: String) -> String |
1861 | | /// @category Core |
1862 | | /// Read a line of input from stdin, optionally displaying a prompt. |
1863 | | /// @example input("Name: ") // reads user input |
1864 | 2.37k | if (strcmp(fn_name, "input") == 0) { |
1865 | 0 | const char *prompt = NULL; |
1866 | 0 | if (argc > 0 && args[0].type == VAL_STR) prompt = args[0].as.str_val; |
1867 | 0 | char *line = builtin_input(prompt); |
1868 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
1869 | 0 | free(args); |
1870 | 0 | if (!line) return eval_ok(value_unit()); |
1871 | 0 | return eval_ok(value_string_owned(line)); |
1872 | 0 | } |
1873 | | |
1874 | | /// @builtin is_complete(source: String) -> Bool |
1875 | | /// @category Metaprogramming |
1876 | | /// Check if a source string is a complete expression (balanced brackets). |
1877 | | /// @example is_complete("{ 1 + 2 }") // true |
1878 | 2.37k | if (strcmp(fn_name, "is_complete") == 0) { |
1879 | 4 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("is_complete() expects 1 string argument")); } |
1880 | 4 | const char *source = args[0].as.str_val; |
1881 | 4 | Lexer lex = lexer_new(source); |
1882 | 4 | char *lex_err = NULL; |
1883 | 4 | LatVec toks = lexer_tokenize(&lex, &lex_err); |
1884 | 4 | if (lex_err) { |
1885 | | /* Lex error (unclosed string etc.) = incomplete */ |
1886 | 0 | free(lex_err); |
1887 | 0 | lat_vec_free(&toks); |
1888 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
1889 | 0 | free(args); |
1890 | 0 | return eval_ok(value_bool(false)); |
1891 | 0 | } |
1892 | 4 | int depth = 0; |
1893 | 23 | for (size_t j = 0; j < toks.len; j++) { |
1894 | 19 | Token *t = lat_vec_get(&toks, j); |
1895 | 19 | switch (t->type) { |
1896 | 4 | case TOK_LBRACE: case TOK_LPAREN: case TOK_LBRACKET: |
1897 | 4 | depth++; |
1898 | 4 | break; |
1899 | 3 | case TOK_RBRACE: case TOK_RPAREN: case TOK_RBRACKET: |
1900 | 3 | depth--; |
1901 | 3 | break; |
1902 | 12 | default: |
1903 | 12 | break; |
1904 | 19 | } |
1905 | 19 | } |
1906 | 23 | for (size_t j = 0; j < toks.len; j++) token_free(lat_vec_get(&toks, j)); |
1907 | 4 | lat_vec_free(&toks); |
1908 | 8 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
1909 | 4 | free(args); |
1910 | 4 | return eval_ok(value_bool(depth <= 0)); |
1911 | 4 | } |
1912 | | |
1913 | | /// @builtin typeof(val: Any) -> String |
1914 | | /// @category Core |
1915 | | /// Returns the type name of a value as a string. |
1916 | | /// @example typeof(42) // "Int" |
1917 | 2.37k | if (strcmp(fn_name, "typeof") == 0) { |
1918 | 72 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("typeof() expects 1 argument")); } |
1919 | 72 | const char *tn = builtin_typeof_str(&args[0]); |
1920 | 144 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
1921 | 72 | free(args); |
1922 | 72 | return eval_ok(value_string(tn)); |
1923 | 72 | } |
1924 | | |
1925 | | /// @builtin struct_name(val: Struct) -> String |
1926 | | /// @category Reflection |
1927 | | /// Returns the type name of a struct instance. |
1928 | | /// @example struct_name(user) // "User" |
1929 | 2.30k | if (strcmp(fn_name, "struct_name") == 0) { |
1930 | 1 | if (argc != 1 || args[0].type != VAL_STRUCT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("struct_name() expects 1 Struct argument")); } |
1931 | 1 | const char *sn = args[0].as.strct.name; |
1932 | 1 | LatValue result = value_string(sn); |
1933 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
1934 | 1 | free(args); |
1935 | 1 | return eval_ok(result); |
1936 | 1 | } |
1937 | | |
1938 | | /// @builtin struct_fields(val: Struct) -> Array |
1939 | | /// @category Reflection |
1940 | | /// Returns an array of field name strings from a struct instance. |
1941 | | /// @example struct_fields(user) // ["name", "age"] |
1942 | 2.30k | if (strcmp(fn_name, "struct_fields") == 0) { |
1943 | 1 | if (argc != 1 || args[0].type != VAL_STRUCT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("struct_fields() expects 1 Struct argument")); } |
1944 | 1 | size_t fc = args[0].as.strct.field_count; |
1945 | 1 | LatValue *elems = malloc((fc > 0 ? fc : 1) * sizeof(LatValue)); |
1946 | 3 | for (size_t j = 0; j < fc; j++) { |
1947 | 2 | elems[j] = value_string(args[0].as.strct.field_names[j]); |
1948 | 2 | } |
1949 | 1 | LatValue result = value_array(elems, fc); |
1950 | 1 | free(elems); |
1951 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
1952 | 1 | free(args); |
1953 | 1 | return eval_ok(result); |
1954 | 1 | } |
1955 | | |
1956 | | /// @builtin struct_to_map(val: Struct) -> Map |
1957 | | /// @category Reflection |
1958 | | /// Converts a struct instance to a Map of {field_name: value}. |
1959 | | /// @example struct_to_map(user).get("name") // "Alice" |
1960 | 2.30k | if (strcmp(fn_name, "struct_to_map") == 0) { |
1961 | 2 | if (argc != 1 || args[0].type != VAL_STRUCT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("struct_to_map() expects 1 Struct argument")); } |
1962 | 2 | LatValue map = value_map_new(); |
1963 | 2 | size_t fc = args[0].as.strct.field_count; |
1964 | 6 | for (size_t j = 0; j < fc; j++) { |
1965 | 4 | LatValue v = value_deep_clone(&args[0].as.strct.field_values[j]); |
1966 | 4 | lat_map_set(map.as.map.map, args[0].as.strct.field_names[j], &v); |
1967 | 4 | } |
1968 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
1969 | 2 | free(args); |
1970 | 2 | return eval_ok(map); |
1971 | 2 | } |
1972 | | |
1973 | | /// @builtin struct_from_map(name: String, map: Map) -> Struct |
1974 | | /// @category Reflection |
1975 | | /// Creates a struct instance from a type name and a Map of field values. |
1976 | | /// Missing fields default to nil. |
1977 | | /// @example struct_from_map("User", m) |
1978 | 2.29k | if (strcmp(fn_name, "struct_from_map") == 0) { |
1979 | 3 | if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_MAP) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("struct_from_map() expects (name: String, map: Map)")); } |
1980 | 3 | const char *sname = args[0].as.str_val; |
1981 | 3 | StructDecl *sd = find_struct(ev, sname); |
1982 | 3 | if (!sd) { |
1983 | 1 | char *err = NULL; |
1984 | 1 | (void)asprintf(&err, "struct_from_map: undefined struct '%s'", sname); |
1985 | 3 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
1986 | 1 | free(args); |
1987 | 1 | return eval_err(err); |
1988 | 1 | } |
1989 | 2 | size_t fc = sd->field_count; |
1990 | 2 | char **names = malloc(fc * sizeof(char *)); |
1991 | 2 | LatValue *vals = malloc(fc * sizeof(LatValue)); |
1992 | 6 | for (size_t j = 0; j < fc; j++) { |
1993 | 4 | names[j] = sd->fields[j].name; |
1994 | 4 | LatValue *found = (LatValue *)lat_map_get(args[1].as.map.map, sd->fields[j].name); |
1995 | 4 | if (found) { |
1996 | 3 | vals[j] = value_deep_clone(found); |
1997 | 3 | } else { |
1998 | 1 | vals[j] = value_nil(); |
1999 | 1 | } |
2000 | 4 | } |
2001 | 2 | stats_struct(&ev->stats); |
2002 | 2 | LatValue st = value_struct(sname, names, vals, fc); |
2003 | 2 | free(names); |
2004 | 2 | free(vals); |
2005 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2006 | 2 | free(args); |
2007 | 2 | return eval_ok(st); |
2008 | 3 | } |
2009 | | |
2010 | | /// @builtin phase_of(val: Any) -> String |
2011 | | /// @category Core |
2012 | | /// Returns the phase of a value ("flux", "fix", or "crystal"). |
2013 | | /// @example phase_of(freeze([1, 2])) // "crystal" |
2014 | 2.29k | if (strcmp(fn_name, "phase_of") == 0) { |
2015 | 29 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("phase_of() expects 1 argument")); } |
2016 | 29 | const char *pn = builtin_phase_of_str(&args[0]); |
2017 | 58 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2018 | 29 | free(args); |
2019 | 29 | return eval_ok(value_string(pn)); |
2020 | 29 | } |
2021 | | |
2022 | | /// @builtin to_string(val: Any) -> String |
2023 | | /// @category Core |
2024 | | /// Convert any value to its string representation. |
2025 | | /// @example to_string(42) // "42" |
2026 | 2.26k | if (strcmp(fn_name, "to_string") == 0) { |
2027 | 177 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("to_string() expects 1 argument")); } |
2028 | 177 | char *s = builtin_to_string(&args[0]); |
2029 | 354 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2030 | 177 | free(args); |
2031 | 177 | return eval_ok(value_string_owned(s)); |
2032 | 177 | } |
2033 | | |
2034 | | /// @builtin repr(val: Any) -> String |
2035 | | /// @category Core |
2036 | | /// Return the repr string of a value. Strings are quoted, |
2037 | | /// structs with a `repr` closure field use the custom representation. |
2038 | | /// @example repr(42) // "42" |
2039 | | /// @example repr("hello") // "\"hello\"" |
2040 | 2.08k | if (strcmp(fn_name, "repr") == 0) { |
2041 | 12 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("repr() expects 1 argument")); } |
2042 | 12 | char *s = eval_repr(ev, &args[0]); |
2043 | 24 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2044 | 12 | free(args); |
2045 | 12 | return eval_ok(value_string_owned(s)); |
2046 | 12 | } |
2047 | | |
2048 | | /// @builtin track(name: String) -> Unit |
2049 | | /// @category Temporal |
2050 | | /// Enable phase history tracking for a variable. |
2051 | | /// @example track("counter") |
2052 | 2.07k | if (strcmp(fn_name, "track") == 0) { |
2053 | 14 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("track() expects 1 String argument")); } |
2054 | 14 | const char *vname = args[0].as.str_val; |
2055 | 14 | LatValue cur; |
2056 | 14 | if (!env_get(ev->env, vname, &cur)) { |
2057 | 1 | char *err = NULL; (void)asprintf(&err, "track(): undefined variable '%s'", vname); |
2058 | 2 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
2059 | 1 | return eval_err(err); |
2060 | 1 | } |
2061 | | /* Check if already tracked */ |
2062 | 14 | bool already = false; |
2063 | 13 | for (size_t i = 0; i < ev->tracked_count; i++) { |
2064 | 0 | if (strcmp(ev->tracked_vars[i].name, vname) == 0) { already = true; break; } |
2065 | 0 | } |
2066 | 13 | if (!already) { |
2067 | 13 | if (ev->tracked_count >= ev->tracked_cap) { |
2068 | 13 | ev->tracked_cap = ev->tracked_cap ? ev->tracked_cap * 2 : 4; |
2069 | 13 | ev->tracked_vars = realloc(ev->tracked_vars, ev->tracked_cap * sizeof(TrackedVar)); |
2070 | 13 | } |
2071 | 13 | TrackedVar *tv = &ev->tracked_vars[ev->tracked_count++]; |
2072 | 13 | tv->name = strdup(vname); |
2073 | 13 | tv->history.snapshots = NULL; |
2074 | 13 | tv->history.count = 0; |
2075 | 13 | tv->history.cap = 0; |
2076 | | /* Record initial snapshot */ |
2077 | 13 | const char *phase = builtin_phase_of_str(&cur); |
2078 | 13 | if (tv->history.count >= tv->history.cap) { |
2079 | 13 | tv->history.cap = tv->history.cap ? tv->history.cap * 2 : 8; |
2080 | 13 | tv->history.snapshots = realloc(tv->history.snapshots, tv->history.cap * sizeof(HistorySnapshot)); |
2081 | 13 | } |
2082 | 13 | tv->history.snapshots[tv->history.count].phase_name = strdup(phase); |
2083 | 13 | tv->history.snapshots[tv->history.count].value = value_deep_clone(&cur); |
2084 | 13 | tv->history.snapshots[tv->history.count].line = 0; |
2085 | 13 | tv->history.snapshots[tv->history.count].fn_name = NULL; |
2086 | 13 | tv->history.count++; |
2087 | 13 | } |
2088 | 13 | value_free(&cur); |
2089 | 26 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
2090 | 13 | return eval_ok(value_unit()); |
2091 | 14 | } |
2092 | | |
2093 | | /// @builtin phases(name: String) -> Array |
2094 | | /// @category Temporal |
2095 | | /// Returns the phase history of a tracked variable as an array of Maps. |
2096 | | /// @example phases("counter") // [{phase: "fluid", value: 0}, ...] |
2097 | 2.06k | if (strcmp(fn_name, "phases") == 0) { |
2098 | 8 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("phases() expects 1 String argument")); } |
2099 | 8 | const char *vname = args[0].as.str_val; |
2100 | 8 | VariableHistory *vh = NULL; |
2101 | 8 | for (size_t i = 0; i < ev->tracked_count; i++) { |
2102 | 7 | if (strcmp(ev->tracked_vars[i].name, vname) == 0) { vh = &ev->tracked_vars[i].history; break; } |
2103 | 7 | } |
2104 | 16 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
2105 | 8 | if (!vh || vh->count == 0) return eval_ok(value_array(NULL, 0)); |
2106 | 7 | LatValue *elems = malloc(vh->count * sizeof(LatValue)); |
2107 | 23 | for (size_t i = 0; i < vh->count; i++) { |
2108 | 16 | LatValue m = value_map_new(); |
2109 | 16 | LatValue pv = value_string(vh->snapshots[i].phase_name); |
2110 | 16 | LatValue vv = value_deep_clone(&vh->snapshots[i].value); |
2111 | 16 | LatValue lv = value_int(vh->snapshots[i].line); |
2112 | 16 | LatValue fv = vh->snapshots[i].fn_name ? value_string(vh->snapshots[i].fn_name) : value_nil(); |
2113 | 16 | lat_map_set(m.as.map.map, "phase", &pv); |
2114 | 16 | lat_map_set(m.as.map.map, "value", &vv); |
2115 | 16 | lat_map_set(m.as.map.map, "line", &lv); |
2116 | 16 | lat_map_set(m.as.map.map, "fn", &fv); |
2117 | 16 | elems[i] = m; |
2118 | 16 | } |
2119 | 7 | LatValue result = value_array(elems, vh->count); |
2120 | 7 | free(elems); |
2121 | 7 | return eval_ok(result); |
2122 | 8 | } |
2123 | | |
2124 | | /// @builtin history(name: String) -> Array |
2125 | | /// @category Temporal |
2126 | | /// Returns the full enriched timeline of a tracked variable as an array of Maps |
2127 | | /// with keys: phase, value, line, fn. |
2128 | | /// @example history(x) // [{phase: "fluid", value: 10, line: 3, fn: "main"}, ...] |
2129 | 2.05k | if (strcmp(fn_name, "history") == 0) { |
2130 | 4 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("history() expects 1 String argument")); } |
2131 | 4 | const char *vname = args[0].as.str_val; |
2132 | 4 | VariableHistory *vh = NULL; |
2133 | 4 | for (size_t i = 0; i < ev->tracked_count; i++) { |
2134 | 3 | if (strcmp(ev->tracked_vars[i].name, vname) == 0) { vh = &ev->tracked_vars[i].history; break; } |
2135 | 3 | } |
2136 | 8 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
2137 | 4 | if (!vh || vh->count == 0) return eval_ok(value_array(NULL, 0)); |
2138 | 3 | LatValue *elems = malloc(vh->count * sizeof(LatValue)); |
2139 | 10 | for (size_t i = 0; i < vh->count; i++) { |
2140 | 7 | LatValue m = value_map_new(); |
2141 | 7 | LatValue pv = value_string(vh->snapshots[i].phase_name); |
2142 | 7 | LatValue vv = value_deep_clone(&vh->snapshots[i].value); |
2143 | 7 | LatValue lv = value_int(vh->snapshots[i].line); |
2144 | 7 | LatValue fv = vh->snapshots[i].fn_name ? value_string(vh->snapshots[i].fn_name) : value_nil(); |
2145 | 7 | lat_map_set(m.as.map.map, "phase", &pv); |
2146 | 7 | lat_map_set(m.as.map.map, "value", &vv); |
2147 | 7 | lat_map_set(m.as.map.map, "line", &lv); |
2148 | 7 | lat_map_set(m.as.map.map, "fn", &fv); |
2149 | 7 | elems[i] = m; |
2150 | 7 | } |
2151 | 3 | LatValue result = value_array(elems, vh->count); |
2152 | 3 | free(elems); |
2153 | 3 | return eval_ok(result); |
2154 | 4 | } |
2155 | | |
2156 | | /// @builtin rewind(name: String, n: Int) -> Any |
2157 | | /// @category Temporal |
2158 | | /// Returns a deep copy of a tracked variable from n steps ago. |
2159 | | /// @example rewind("counter", 2) // value from 2 steps back |
2160 | 2.05k | if (strcmp(fn_name, "rewind") == 0) { |
2161 | 7 | if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("rewind() expects (String, Int)")); } |
2162 | 7 | const char *vname = args[0].as.str_val; |
2163 | 7 | int64_t steps = args[1].as.int_val; |
2164 | 7 | VariableHistory *vh = NULL; |
2165 | 7 | for (size_t i = 0; i < ev->tracked_count; i++) { |
2166 | 7 | if (strcmp(ev->tracked_vars[i].name, vname) == 0) { vh = &ev->tracked_vars[i].history; break; } |
2167 | 7 | } |
2168 | 21 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
2169 | 7 | if (!vh || steps < 0 || (size_t)steps >= vh->count) |
2170 | 1 | return eval_ok(value_nil()); |
2171 | 6 | size_t idx = vh->count - 1 - (size_t)steps; |
2172 | 6 | return eval_ok(value_deep_clone(&vh->snapshots[idx].value)); |
2173 | 7 | } |
2174 | | |
2175 | | /// @builtin grow(name: String) -> Any |
2176 | | /// @category Phase Transitions |
2177 | | /// Freeze a variable and validate any pending seed contracts. |
2178 | | /// @example grow(config) // freeze + validate seeds |
2179 | 2.04k | if (strcmp(fn_name, "grow") == 0) { |
2180 | 2 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("grow() expects 1 String argument (variable name)")); } |
2181 | 2 | const char *vname = args[0].as.str_val; |
2182 | 2 | LatValue val; |
2183 | 2 | if (!env_get(ev->env, vname, &val)) { |
2184 | 0 | char *err = NULL; (void)asprintf(&err, "grow(): undefined variable '%s'", vname); |
2185 | 0 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
2186 | 0 | return eval_err(err); |
2187 | 0 | } |
2188 | | /* Check and validate all seeds for this variable */ |
2189 | 3 | for (size_t si = 0; si < ev->seed_count; si++) { |
2190 | 2 | if (strcmp(ev->seeds[si].var_name, vname) != 0) continue; |
2191 | 2 | LatValue check_val = value_deep_clone(&val); |
2192 | 2 | LatValue *cb = &ev->seeds[si].contract; |
2193 | 2 | EvalResult vr = call_closure(ev, cb->as.closure.param_names, |
2194 | 2 | cb->as.closure.param_count, cb->as.closure.body, |
2195 | 2 | cb->as.closure.captured_env, &check_val, 1, |
2196 | 2 | cb->as.closure.default_values, cb->as.closure.has_variadic); |
2197 | 2 | if (!IS_OK(vr)) { |
2198 | 0 | char *msg = NULL; |
2199 | 0 | (void)asprintf(&msg, "grow() seed contract failed: %s", vr.error); |
2200 | 0 | free(vr.error); value_free(&val); |
2201 | 0 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
2202 | 0 | return eval_err(msg); |
2203 | 0 | } |
2204 | 2 | if (!value_is_truthy(&vr.value)) { |
2205 | 1 | value_free(&vr.value); value_free(&val); |
2206 | 2 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
2207 | 1 | return eval_err(strdup("grow() seed contract returned false")); |
2208 | 1 | } |
2209 | 1 | value_free(&vr.value); |
2210 | | /* Remove this seed */ |
2211 | 1 | free(ev->seeds[si].var_name); |
2212 | 1 | value_free(&ev->seeds[si].contract); |
2213 | 1 | ev->seeds[si] = ev->seeds[--ev->seed_count]; |
2214 | 1 | si--; /* re-check this index */ |
2215 | 1 | } |
2216 | | /* Freeze the variable */ |
2217 | 1 | val = value_freeze(val); |
2218 | 1 | freeze_to_region(ev, &val); |
2219 | 1 | LatValue ret = value_deep_clone(&val); |
2220 | 1 | env_set(ev->env, vname, val); |
2221 | 1 | record_history(ev, vname); |
2222 | 1 | { |
2223 | 1 | char *cascade_err = freeze_cascade(ev, vname); |
2224 | 1 | if (cascade_err) { |
2225 | 0 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
2226 | 0 | value_free(&ret); |
2227 | 0 | return eval_err(cascade_err); |
2228 | 0 | } |
2229 | 1 | } |
2230 | 1 | EvalResult fr = fire_reactions(ev, vname, "crystal"); |
2231 | 2 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
2232 | 1 | if (!IS_OK(fr)) { value_free(&ret); return fr; } |
2233 | 1 | return eval_ok(ret); |
2234 | 1 | } |
2235 | | |
2236 | | /// @builtin pressure_of(name: String) -> String|Nil |
2237 | | /// @category Phase Pressure |
2238 | | /// Returns the current pressure mode of a variable, or nil if none. |
2239 | | /// @example pressure_of("data") // "no_grow" |
2240 | 2.04k | if (strcmp(fn_name, "pressure_of") == 0) { |
2241 | 2 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("pressure_of() expects 1 String argument")); } |
2242 | 2 | const char *vname = args[0].as.str_val; |
2243 | 2 | for (size_t i = 0; i < ev->pressure_count; i++) { |
2244 | 1 | if (strcmp(ev->pressures[i].var_name, vname) == 0) { |
2245 | 1 | LatValue result = value_string(ev->pressures[i].mode); |
2246 | 2 | for (size_t j = 0; j < argc; j++) { value_free(&args[j]); } free(args); |
2247 | 1 | return eval_ok(result); |
2248 | 1 | } |
2249 | 1 | } |
2250 | 2 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
2251 | 1 | return eval_ok(value_nil()); |
2252 | 2 | } |
2253 | | |
2254 | | /// @builtin ord(ch: String) -> Int |
2255 | | /// @category Type Conversion |
2256 | | /// Return the Unicode code point of the first character. |
2257 | | /// @example ord("A") // 65 |
2258 | 2.04k | if (strcmp(fn_name, "ord") == 0) { |
2259 | 4 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("ord() expects 1 string argument")); } |
2260 | 4 | int64_t code = builtin_ord(args[0].as.str_val); |
2261 | 8 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2262 | 4 | free(args); |
2263 | 4 | return eval_ok(value_int(code)); |
2264 | 4 | } |
2265 | | |
2266 | | /// @builtin chr(code: Int) -> String |
2267 | | /// @category Type Conversion |
2268 | | /// Return the character for a Unicode code point. |
2269 | | /// @example chr(65) // "A" |
2270 | 2.03k | if (strcmp(fn_name, "chr") == 0) { |
2271 | 2 | if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("chr() expects 1 integer argument")); } |
2272 | 2 | char *s = builtin_chr(args[0].as.int_val); |
2273 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2274 | 2 | free(args); |
2275 | 2 | return eval_ok(value_string_owned(s)); |
2276 | 2 | } |
2277 | | |
2278 | | /// @builtin read_file(path: String) -> String |
2279 | | /// @category File System |
2280 | | /// Read the entire contents of a file as a string. |
2281 | | /// @example read_file("data.txt") // "file contents..." |
2282 | 2.03k | if (strcmp(fn_name, "read_file") == 0) { |
2283 | 4 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("read_file() expects 1 string argument")); } |
2284 | 4 | char *contents = builtin_read_file(args[0].as.str_val); |
2285 | 8 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2286 | 4 | free(args); |
2287 | 4 | if (!contents) return eval_err(strdup("read_file: could not read file")); |
2288 | 4 | return eval_ok(value_string_owned(contents)); |
2289 | 4 | } |
2290 | | |
2291 | | /// @builtin write_file(path: String, content: String) -> Bool |
2292 | | /// @category File System |
2293 | | /// Write a string to a file, creating or overwriting it. |
2294 | | /// @example write_file("out.txt", "hello") // true |
2295 | 2.03k | if (strcmp(fn_name, "write_file") == 0) { |
2296 | 13 | if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("write_file() expects 2 string arguments")); } |
2297 | 13 | bool wf_ok = builtin_write_file(args[0].as.str_val, args[1].as.str_val); |
2298 | 39 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2299 | 13 | free(args); |
2300 | 13 | if (!wf_ok) return eval_err(strdup("write_file: could not write file")); |
2301 | 13 | return eval_ok(value_bool(true)); |
2302 | 13 | } |
2303 | | |
2304 | | /// @builtin file_exists(path: String) -> Bool |
2305 | | /// @category File System |
2306 | | /// Check if a file or directory exists at the given path. |
2307 | | /// @example file_exists("data.txt") // true |
2308 | 2.01k | if (strcmp(fn_name, "file_exists") == 0) { |
2309 | 8 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("file_exists() expects 1 string argument")); } |
2310 | 8 | bool exists = fs_file_exists(args[0].as.str_val); |
2311 | 14 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2312 | 7 | free(args); |
2313 | 7 | return eval_ok(value_bool(exists)); |
2314 | 8 | } |
2315 | | |
2316 | | /// @builtin delete_file(path: String) -> Bool |
2317 | | /// @category File System |
2318 | | /// Delete a file at the given path. |
2319 | | /// @example delete_file("temp.txt") // true |
2320 | 2.00k | if (strcmp(fn_name, "delete_file") == 0) { |
2321 | 13 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("delete_file() expects 1 string argument")); } |
2322 | 12 | char *df_err = NULL; |
2323 | 12 | bool df_ok = fs_delete_file(args[0].as.str_val, &df_err); |
2324 | 24 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2325 | 12 | free(args); |
2326 | 12 | if (!df_ok) { char *e = df_err; return eval_err(e); } |
2327 | 11 | return eval_ok(value_bool(true)); |
2328 | 12 | } |
2329 | | |
2330 | | /// @builtin list_dir(path: String) -> Array |
2331 | | /// @category File System |
2332 | | /// List entries in a directory, returning an array of filenames. |
2333 | | /// @example list_dir(".") // ["file1.txt", "dir1", ...] |
2334 | 1.99k | if (strcmp(fn_name, "list_dir") == 0) { |
2335 | 3 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("list_dir() expects 1 string argument")); } |
2336 | 2 | char *ld_err = NULL; |
2337 | 2 | size_t ld_count = 0; |
2338 | 2 | char **ld_entries = fs_list_dir(args[0].as.str_val, &ld_count, &ld_err); |
2339 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2340 | 2 | free(args); |
2341 | 2 | if (!ld_entries) { char *e = ld_err; return eval_err(e); } |
2342 | 1 | LatValue *elems = malloc(ld_count * sizeof(LatValue)); |
2343 | 1.62k | for (size_t i = 0; i < ld_count; i++) { |
2344 | 1.62k | elems[i] = value_string(ld_entries[i]); |
2345 | 1.62k | free(ld_entries[i]); |
2346 | 1.62k | } |
2347 | 1 | free(ld_entries); |
2348 | 1 | LatValue arr = value_array(elems, ld_count); |
2349 | 1 | free(elems); |
2350 | 1 | return eval_ok(arr); |
2351 | 2 | } |
2352 | | |
2353 | | /// @builtin read_file_bytes(path: String) -> Buffer |
2354 | | /// @category File System |
2355 | | /// Read the entire contents of a file as a Buffer. |
2356 | | /// @example read_file_bytes("data.bin") // Buffer<...> |
2357 | 1.99k | if (strcmp(fn_name, "read_file_bytes") == 0) { |
2358 | 0 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("read_file_bytes() expects 1 string argument")); } |
2359 | 0 | FILE *bf = fopen(args[0].as.str_val, "rb"); |
2360 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2361 | 0 | free(args); |
2362 | 0 | if (!bf) return eval_err(strdup("read_file_bytes: could not read file")); |
2363 | 0 | fseek(bf, 0, SEEK_END); |
2364 | 0 | long bflen = ftell(bf); |
2365 | 0 | fseek(bf, 0, SEEK_SET); |
2366 | 0 | if (bflen < 0) { fclose(bf); return eval_err(strdup("read_file_bytes: could not read file")); } |
2367 | 0 | uint8_t *bfdata = malloc((size_t)bflen); |
2368 | 0 | size_t bfnread = fread(bfdata, 1, (size_t)bflen, bf); |
2369 | 0 | fclose(bf); |
2370 | 0 | LatValue buf = value_buffer(bfdata, bfnread); |
2371 | 0 | free(bfdata); |
2372 | 0 | return eval_ok(buf); |
2373 | 0 | } |
2374 | | |
2375 | | /// @builtin write_file_bytes(path: String, buffer: Buffer) -> Bool |
2376 | | /// @category File System |
2377 | | /// Write a Buffer to a file. |
2378 | | /// @example write_file_bytes("out.bin", buf) // true |
2379 | 1.99k | if (strcmp(fn_name, "write_file_bytes") == 0) { |
2380 | 0 | if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_BUFFER) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("write_file_bytes() expects (String, Buffer)")); } |
2381 | 0 | FILE *wbf = fopen(args[0].as.str_val, "wb"); |
2382 | 0 | if (!wbf) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("write_file_bytes: could not write file")); } |
2383 | 0 | size_t wbwritten = fwrite(args[1].as.buffer.data, 1, args[1].as.buffer.len, wbf); |
2384 | 0 | fclose(wbf); |
2385 | 0 | bool wbok = (wbwritten == args[1].as.buffer.len); |
2386 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2387 | 0 | free(args); |
2388 | 0 | return eval_ok(value_bool(wbok)); |
2389 | 0 | } |
2390 | | |
2391 | | /// @builtin append_file(path: String, content: String) -> Bool |
2392 | | /// @category File System |
2393 | | /// Append a string to the end of a file. |
2394 | | /// @example append_file("log.txt", "new line\n") // true |
2395 | 1.99k | if (strcmp(fn_name, "append_file") == 0) { |
2396 | 3 | if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("append_file() expects 2 string arguments")); } |
2397 | 2 | char *af_err = NULL; |
2398 | 2 | bool af_ok = fs_append_file(args[0].as.str_val, args[1].as.str_val, &af_err); |
2399 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2400 | 2 | free(args); |
2401 | 2 | if (!af_ok) { char *e = af_err; return eval_err(e); } |
2402 | 2 | return eval_ok(value_bool(true)); |
2403 | 2 | } |
2404 | | |
2405 | | /// @builtin mkdir(path: String) -> Bool |
2406 | | /// @category File System |
2407 | | /// Create a directory at the given path. |
2408 | | /// @example mkdir("new_dir") // true |
2409 | 1.99k | if (strcmp(fn_name, "mkdir") == 0) { |
2410 | 3 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("mkdir() expects 1 string argument")); } |
2411 | 3 | char *mk_err = NULL; |
2412 | 3 | bool mk_ok = fs_mkdir(args[0].as.str_val, &mk_err); |
2413 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2414 | 3 | free(args); |
2415 | 3 | if (!mk_ok) { free(mk_err); return eval_ok(value_bool(false)); } |
2416 | 3 | return eval_ok(value_bool(true)); |
2417 | 3 | } |
2418 | | |
2419 | | /// @builtin rename(old_path: String, new_path: String) -> Bool |
2420 | | /// @category File System |
2421 | | /// Rename or move a file or directory. |
2422 | | /// @example rename("old.txt", "new.txt") // true |
2423 | 1.98k | if (strcmp(fn_name, "rename") == 0) { |
2424 | 1 | if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("rename() expects 2 string arguments")); } |
2425 | 1 | char *rn_err = NULL; |
2426 | 1 | bool rn_ok = fs_rename(args[0].as.str_val, args[1].as.str_val, &rn_err); |
2427 | 3 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2428 | 1 | free(args); |
2429 | 1 | if (!rn_ok) { free(rn_err); return eval_ok(value_bool(false)); } |
2430 | 1 | return eval_ok(value_bool(true)); |
2431 | 1 | } |
2432 | | |
2433 | | /// @builtin is_dir(path: String) -> Bool |
2434 | | /// @category File System |
2435 | | /// Check if the path points to a directory. |
2436 | | /// @example is_dir("/tmp") // true |
2437 | 1.98k | if (strcmp(fn_name, "is_dir") == 0) { |
2438 | 5 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("is_dir() expects 1 string argument")); } |
2439 | 5 | bool result = fs_is_dir(args[0].as.str_val); |
2440 | 10 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2441 | 5 | free(args); |
2442 | 5 | return eval_ok(value_bool(result)); |
2443 | 5 | } |
2444 | | |
2445 | | /// @builtin is_file(path: String) -> Bool |
2446 | | /// @category File System |
2447 | | /// Check if the path points to a regular file. |
2448 | | /// @example is_file("data.txt") // true |
2449 | 1.98k | if (strcmp(fn_name, "is_file") == 0) { |
2450 | 3 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("is_file() expects 1 string argument")); } |
2451 | 3 | bool result = fs_is_file(args[0].as.str_val); |
2452 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2453 | 3 | free(args); |
2454 | 3 | return eval_ok(value_bool(result)); |
2455 | 3 | } |
2456 | | |
2457 | | /// @builtin rmdir(path: String) -> Bool |
2458 | | /// @category File System |
2459 | | /// Remove a directory (must be empty). |
2460 | | /// @example rmdir("old_dir") // true |
2461 | 1.97k | if (strcmp(fn_name, "rmdir") == 0) { |
2462 | 4 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("rmdir() expects 1 string argument")); } |
2463 | 4 | char *rm_err = NULL; |
2464 | 4 | bool rm_ok = fs_rmdir(args[0].as.str_val, &rm_err); |
2465 | 8 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2466 | 4 | free(args); |
2467 | 4 | if (!rm_ok) { char *e = rm_err; return eval_err(e); } |
2468 | 3 | return eval_ok(value_bool(true)); |
2469 | 4 | } |
2470 | | |
2471 | | /// @builtin glob(pattern: String) -> Array |
2472 | | /// @category File System |
2473 | | /// Find files matching a glob pattern, returning an array of paths. |
2474 | | /// @example glob("*.txt") // ["a.txt", "b.txt"] |
2475 | 1.97k | if (strcmp(fn_name, "glob") == 0) { |
2476 | 2 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("glob() expects 1 string argument")); } |
2477 | 2 | char *gl_err = NULL; |
2478 | 2 | size_t gl_count = 0; |
2479 | 2 | char **gl_entries = fs_glob(args[0].as.str_val, &gl_count, &gl_err); |
2480 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2481 | 2 | free(args); |
2482 | 2 | if (gl_err) { return eval_err(gl_err); } |
2483 | | /* Build array of strings */ |
2484 | 2 | LatValue *elems = NULL; |
2485 | 2 | if (gl_count > 0) { |
2486 | 1 | elems = malloc(gl_count * sizeof(LatValue)); |
2487 | 3 | for (size_t i = 0; i < gl_count; i++) { |
2488 | 2 | elems[i] = value_string_owned(gl_entries[i]); |
2489 | 2 | } |
2490 | 1 | free(gl_entries); |
2491 | 1 | } |
2492 | 2 | LatValue arr = value_array(elems, gl_count); |
2493 | 2 | free(elems); |
2494 | 2 | return eval_ok(arr); |
2495 | 2 | } |
2496 | | |
2497 | | /// @builtin stat(path: String) -> Map |
2498 | | /// @category File System |
2499 | | /// Get file metadata (size, mtime, type, permissions) as a map. |
2500 | | /// @example stat("file.txt") // {size: 1024, mtime: ..., type: "file", permissions: 644} |
2501 | 1.97k | if (strcmp(fn_name, "stat") == 0) { |
2502 | 3 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("stat() expects 1 string argument")); } |
2503 | 3 | int64_t st_size = 0, st_mtime = 0, st_mode = 0; |
2504 | 3 | const char *st_type = NULL; |
2505 | 3 | char *st_err = NULL; |
2506 | 3 | bool st_ok = fs_stat(args[0].as.str_val, &st_size, &st_mtime, &st_mode, &st_type, &st_err); |
2507 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2508 | 3 | free(args); |
2509 | 3 | if (!st_ok) { return eval_err(st_err); } |
2510 | | /* Build result Map — lat_map_set does shallow memcpy, |
2511 | | * so the map takes ownership of internal pointers. |
2512 | | * Do NOT value_free the temporaries. */ |
2513 | 2 | LatValue map = value_map_new(); |
2514 | 2 | LatValue sz = value_int(st_size); |
2515 | 2 | lat_map_set(map.as.map.map, "size", &sz); |
2516 | 2 | LatValue mt = value_int(st_mtime); |
2517 | 2 | lat_map_set(map.as.map.map, "mtime", &mt); |
2518 | 2 | LatValue ty = value_string(st_type); |
2519 | 2 | lat_map_set(map.as.map.map, "type", &ty); |
2520 | 2 | LatValue pm = value_int(st_mode); |
2521 | 2 | lat_map_set(map.as.map.map, "permissions", &pm); |
2522 | 2 | return eval_ok(map); |
2523 | 3 | } |
2524 | | |
2525 | | /// @builtin copy_file(src: String, dest: String) -> Bool |
2526 | | /// @category File System |
2527 | | /// Copy a file from source path to destination path. |
2528 | | /// @example copy_file("a.txt", "b.txt") // true |
2529 | 1.96k | if (strcmp(fn_name, "copy_file") == 0) { |
2530 | 2 | if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("copy_file() expects 2 string arguments")); } |
2531 | 2 | char *cp_err = NULL; |
2532 | 2 | bool cp_ok = fs_copy_file(args[0].as.str_val, args[1].as.str_val, &cp_err); |
2533 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2534 | 2 | free(args); |
2535 | 2 | if (!cp_ok) { return eval_err(cp_err); } |
2536 | 1 | return eval_ok(value_bool(true)); |
2537 | 2 | } |
2538 | | |
2539 | | /// @builtin realpath(path: String) -> String |
2540 | | /// @category File System |
2541 | | /// Resolve a path to its absolute canonical form. |
2542 | | /// @example realpath("./src/../src") // "/home/user/src" |
2543 | 1.96k | if (strcmp(fn_name, "realpath") == 0) { |
2544 | 2 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("realpath() expects 1 string argument")); } |
2545 | 2 | char *rp_err = NULL; |
2546 | 2 | char *rp_result = fs_realpath(args[0].as.str_val, &rp_err); |
2547 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2548 | 2 | free(args); |
2549 | 2 | if (!rp_result) { return eval_err(rp_err); } |
2550 | 1 | return eval_ok(value_string_owned(rp_result)); |
2551 | 2 | } |
2552 | | |
2553 | | /// @builtin tempdir() -> String |
2554 | | /// @category File System |
2555 | | /// Create a temporary directory and return its path. |
2556 | | /// @example tempdir() // "/tmp/lat_XXXXXX" |
2557 | 1.96k | if (strcmp(fn_name, "tempdir") == 0) { |
2558 | 1 | if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tempdir() expects no arguments")); } |
2559 | 1 | free(args); |
2560 | 1 | char *td_err = NULL; |
2561 | 1 | char *td_result = fs_tempdir(&td_err); |
2562 | 1 | if (!td_result) { return eval_err(td_err); } |
2563 | 1 | return eval_ok(value_string_owned(td_result)); |
2564 | 1 | } |
2565 | | |
2566 | | /// @builtin tempfile() -> String |
2567 | | /// @category File System |
2568 | | /// Create a temporary file and return its path. |
2569 | | /// @example tempfile() // "/tmp/lat_XXXXXX" |
2570 | 1.96k | if (strcmp(fn_name, "tempfile") == 0) { |
2571 | 2 | if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tempfile() expects no arguments")); } |
2572 | 2 | free(args); |
2573 | 2 | char *tf_err = NULL; |
2574 | 2 | char *tf_result = fs_tempfile(&tf_err); |
2575 | 2 | if (!tf_result) { return eval_err(tf_err); } |
2576 | 2 | return eval_ok(value_string_owned(tf_result)); |
2577 | 2 | } |
2578 | | |
2579 | | /// @builtin chmod(path: String, mode: Int) -> Bool |
2580 | | /// @category File System |
2581 | | /// Change file permissions using a numeric mode. |
2582 | | /// @example chmod("script.sh", 755) // true |
2583 | 1.96k | if (strcmp(fn_name, "chmod") == 0) { |
2584 | 1 | if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("chmod() expects 2 arguments (string path, integer mode)")); } |
2585 | 1 | char *ch_err = NULL; |
2586 | 1 | bool ch_ok = fs_chmod(args[0].as.str_val, (int)args[1].as.int_val, &ch_err); |
2587 | 3 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2588 | 1 | free(args); |
2589 | 1 | if (!ch_ok) { return eval_err(ch_err); } |
2590 | 1 | return eval_ok(value_bool(true)); |
2591 | 1 | } |
2592 | | |
2593 | | /// @builtin file_size(path: String) -> Int |
2594 | | /// @category File System |
2595 | | /// Return the size of a file in bytes. |
2596 | | /// @example file_size("data.bin") // 4096 |
2597 | 1.96k | if (strcmp(fn_name, "file_size") == 0) { |
2598 | 2 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("file_size() expects 1 string argument")); } |
2599 | 2 | char *fs_err = NULL; |
2600 | 2 | int64_t sz = fs_file_size(args[0].as.str_val, &fs_err); |
2601 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2602 | 2 | free(args); |
2603 | 2 | if (sz < 0) { return eval_err(fs_err); } |
2604 | 1 | return eval_ok(value_int(sz)); |
2605 | 2 | } |
2606 | | |
2607 | | /* ── Path builtins ── */ |
2608 | | |
2609 | | /// @builtin path_join(parts: String...) -> String |
2610 | | /// @category Path |
2611 | | /// Join path components into a single path string. |
2612 | | /// @example path_join("/home", "user", "file.txt") // "/home/user/file.txt" |
2613 | 1.95k | if (strcmp(fn_name, "path_join") == 0) { |
2614 | 5 | if (argc < 1) { free(args); return eval_err(strdup("path_join() expects at least 1 argument")); } |
2615 | 14 | for (size_t i = 0; i < argc; i++) { |
2616 | 10 | if (args[i].type != VAL_STR) { for (size_t j = 0; j < argc; j++) { value_free(&args[j]); } free(args); return eval_err(strdup("path_join() expects String arguments")); } |
2617 | 10 | } |
2618 | 4 | const char **parts = malloc(argc * sizeof(char*)); |
2619 | 13 | for (size_t i = 0; i < argc; i++) parts[i] = args[i].as.str_val; |
2620 | 4 | char *result = path_join(parts, argc); |
2621 | 4 | free(parts); |
2622 | 13 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2623 | 4 | free(args); |
2624 | 4 | return eval_ok(value_string_owned(result)); |
2625 | 5 | } |
2626 | | |
2627 | | /// @builtin path_dir(path: String) -> String |
2628 | | /// @category Path |
2629 | | /// Return the directory component of a path. |
2630 | | /// @example path_dir("/home/user/file.txt") // "/home/user" |
2631 | 1.95k | if (strcmp(fn_name, "path_dir") == 0) { |
2632 | 5 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("path_dir() expects 1 String argument")); } |
2633 | 4 | char *result = path_dir(args[0].as.str_val); |
2634 | 8 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2635 | 4 | free(args); |
2636 | 4 | return eval_ok(value_string_owned(result)); |
2637 | 5 | } |
2638 | | |
2639 | | /// @builtin path_base(path: String) -> String |
2640 | | /// @category Path |
2641 | | /// Return the filename component of a path. |
2642 | | /// @example path_base("/home/user/file.txt") // "file.txt" |
2643 | 1.94k | if (strcmp(fn_name, "path_base") == 0) { |
2644 | 4 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("path_base() expects 1 String argument")); } |
2645 | 3 | char *result = path_base(args[0].as.str_val); |
2646 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2647 | 3 | free(args); |
2648 | 3 | return eval_ok(value_string_owned(result)); |
2649 | 4 | } |
2650 | | |
2651 | | /// @builtin path_ext(path: String) -> String |
2652 | | /// @category Path |
2653 | | /// Return the file extension of a path (including the dot). |
2654 | | /// @example path_ext("file.txt") // ".txt" |
2655 | 1.94k | if (strcmp(fn_name, "path_ext") == 0) { |
2656 | 6 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("path_ext() expects 1 String argument")); } |
2657 | 5 | char *result = path_ext(args[0].as.str_val); |
2658 | 10 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2659 | 5 | free(args); |
2660 | 5 | return eval_ok(value_string_owned(result)); |
2661 | 6 | } |
2662 | | |
2663 | | /// @builtin require(path: String) -> Bool |
2664 | | /// @category Metaprogramming |
2665 | | /// Load and execute a Lattice source file, importing its definitions. |
2666 | | /// @example require("stdlib.lat") // true |
2667 | 1.93k | if (strcmp(fn_name, "require") == 0) { |
2668 | 9 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("require() expects 1 string argument")); } |
2669 | 9 | const char *raw_path = args[0].as.str_val; |
2670 | | /* Build the file path: append .lat if not already present */ |
2671 | 9 | size_t plen = strlen(raw_path); |
2672 | 9 | char *file_path; |
2673 | 9 | if (plen >= 4 && strcmp(raw_path + plen - 4, ".lat") == 0) { |
2674 | 2 | file_path = strdup(raw_path); |
2675 | 7 | } else { |
2676 | 7 | file_path = malloc(plen + 5); |
2677 | 7 | memcpy(file_path, raw_path, plen); |
2678 | 7 | memcpy(file_path + plen, ".lat", 5); |
2679 | 7 | } |
2680 | | /* Resolve to an absolute path for dedup. |
2681 | | * Try cwd-relative first, then script_dir-relative. */ |
2682 | 9 | char resolved[PATH_MAX]; |
2683 | 9 | bool found = (realpath(file_path, resolved) != NULL); |
2684 | 9 | if (!found && ev->script_dir && file_path[0] != '/') { |
2685 | | /* Try relative to the script's directory */ |
2686 | 0 | char script_rel[PATH_MAX]; |
2687 | 0 | snprintf(script_rel, sizeof(script_rel), "%s/%s", |
2688 | 0 | ev->script_dir, file_path); |
2689 | 0 | found = (realpath(script_rel, resolved) != NULL); |
2690 | 0 | } |
2691 | 9 | if (!found) { |
2692 | 1 | char errbuf[512]; |
2693 | 1 | snprintf(errbuf, sizeof(errbuf), "require: cannot find '%s'", file_path); |
2694 | 1 | free(file_path); |
2695 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2696 | 1 | free(args); |
2697 | 1 | return eval_err(strdup(errbuf)); |
2698 | 1 | } |
2699 | 8 | free(file_path); |
2700 | | /* Skip if already required */ |
2701 | 8 | if (lat_map_get(&ev->required_files, resolved)) { |
2702 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2703 | 2 | free(args); |
2704 | 2 | return eval_ok(value_bool(true)); |
2705 | 2 | } |
2706 | | /* Mark as required before evaluating (guards against circular requires) */ |
2707 | 8 | bool marker = true; |
2708 | 6 | lat_map_set(&ev->required_files, resolved, &marker); |
2709 | | /* Read the file */ |
2710 | 6 | char *source = builtin_read_file(resolved); |
2711 | 6 | if (!source) { |
2712 | 0 | char errbuf[512]; |
2713 | 0 | snprintf(errbuf, sizeof(errbuf), "require: cannot read '%s'", resolved); |
2714 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2715 | 0 | free(args); |
2716 | 0 | return eval_err(strdup(errbuf)); |
2717 | 0 | } |
2718 | | /* Lex */ |
2719 | 6 | Lexer req_lex = lexer_new(source); |
2720 | 6 | char *req_lex_err = NULL; |
2721 | 6 | LatVec req_toks = lexer_tokenize(&req_lex, &req_lex_err); |
2722 | 6 | free(source); |
2723 | 6 | if (req_lex_err) { |
2724 | 0 | char errbuf[1024]; |
2725 | 0 | snprintf(errbuf, sizeof(errbuf), "require '%s': %s", resolved, req_lex_err); |
2726 | 0 | free(req_lex_err); |
2727 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2728 | 0 | free(args); |
2729 | 0 | return eval_err(strdup(errbuf)); |
2730 | 0 | } |
2731 | | /* Parse */ |
2732 | 6 | Parser req_parser = parser_new(&req_toks); |
2733 | 6 | char *req_parse_err = NULL; |
2734 | 6 | Program req_prog = parser_parse(&req_parser, &req_parse_err); |
2735 | 6 | if (req_parse_err) { |
2736 | 0 | char errbuf[1024]; |
2737 | 0 | snprintf(errbuf, sizeof(errbuf), "require '%s': %s", resolved, req_parse_err); |
2738 | 0 | free(req_parse_err); |
2739 | 0 | program_free(&req_prog); |
2740 | 0 | for (size_t j = 0; j < req_toks.len; j++) token_free(lat_vec_get(&req_toks, j)); |
2741 | 0 | lat_vec_free(&req_toks); |
2742 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2743 | 0 | free(args); |
2744 | 0 | return eval_err(strdup(errbuf)); |
2745 | 0 | } |
2746 | | /* Register functions, structs, traits, impls */ |
2747 | 14 | for (size_t j = 0; j < req_prog.item_count; j++) { |
2748 | 8 | if (req_prog.items[j].tag == ITEM_STRUCT) { |
2749 | 1 | StructDecl *ptr = &req_prog.items[j].as.struct_decl; |
2750 | 1 | lat_map_set(&ev->struct_defs, ptr->name, &ptr); |
2751 | 7 | } else if (req_prog.items[j].tag == ITEM_FUNCTION) { |
2752 | 6 | FnDecl *ptr = &req_prog.items[j].as.fn_decl; |
2753 | 6 | register_fn_overload(&ev->fn_defs, ptr); |
2754 | 6 | } else if (req_prog.items[j].tag == ITEM_TRAIT) { |
2755 | 0 | TraitDecl *ptr = &req_prog.items[j].as.trait_decl; |
2756 | 0 | lat_map_set(&ev->trait_defs, ptr->name, &ptr); |
2757 | 1 | } else if (req_prog.items[j].tag == ITEM_IMPL) { |
2758 | 0 | ImplBlock *ptr = &req_prog.items[j].as.impl_block; |
2759 | 0 | char key[512]; |
2760 | 0 | snprintf(key, sizeof(key), "%s::%s", ptr->type_name, ptr->trait_name); |
2761 | 0 | lat_map_set(&ev->impl_registry, key, &ptr); |
2762 | 0 | } |
2763 | 8 | } |
2764 | | /* Set script_dir to the required file's directory for nested requires */ |
2765 | 6 | char *prev_script_dir = ev->script_dir; |
2766 | 6 | char *resolved_copy = strdup(resolved); |
2767 | 6 | ev->script_dir = strdup(dirname(resolved_copy)); |
2768 | 6 | free(resolved_copy); |
2769 | | /* Execute top-level statements */ |
2770 | 6 | size_t saved_scope = ev->lat_eval_scope; |
2771 | 6 | ev->lat_eval_scope = ev->env->count; |
2772 | 6 | EvalResult req_r = eval_ok(value_unit()); |
2773 | 14 | for (size_t j = 0; j < req_prog.item_count; j++) { |
2774 | 8 | if (req_prog.items[j].tag == ITEM_STMT) { |
2775 | 1 | value_free(&req_r.value); |
2776 | 1 | req_r = eval_stmt(ev, req_prog.items[j].as.stmt); |
2777 | 1 | if (!IS_OK(req_r)) break; |
2778 | 1 | } |
2779 | 8 | } |
2780 | 6 | ev->lat_eval_scope = saved_scope; |
2781 | | /* Restore previous script_dir */ |
2782 | 6 | free(ev->script_dir); |
2783 | 6 | ev->script_dir = prev_script_dir; |
2784 | | /* Cleanup: free statements, keep decl items alive */ |
2785 | 6 | bool req_has_decls = false; |
2786 | 14 | for (size_t j = 0; j < req_prog.item_count; j++) { |
2787 | 8 | if (req_prog.items[j].tag == ITEM_STMT) |
2788 | 1 | stmt_free(req_prog.items[j].as.stmt); |
2789 | 7 | else |
2790 | 7 | req_has_decls = true; |
2791 | 8 | } |
2792 | 6 | if (!req_has_decls) free(req_prog.items); |
2793 | 107 | for (size_t j = 0; j < req_toks.len; j++) token_free(lat_vec_get(&req_toks, j)); |
2794 | 6 | lat_vec_free(&req_toks); |
2795 | 12 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2796 | 6 | free(args); |
2797 | 6 | if (!IS_OK(req_r)) return req_r; |
2798 | 6 | value_free(&req_r.value); |
2799 | 6 | return eval_ok(value_bool(true)); |
2800 | 6 | } |
2801 | | |
2802 | | /// @builtin require_ext(name: String) -> Map |
2803 | | /// @category Metaprogramming |
2804 | | /// Load a native extension (.dylib/.so) and return a Map of its functions. |
2805 | | /// @example let pg = require_ext("pg") |
2806 | 1.93k | if (strcmp(fn_name, "require_ext") == 0) { |
2807 | 18 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("require_ext() expects 1 string argument")); } |
2808 | 16 | const char *ext_name = args[0].as.str_val; |
2809 | | /* Check cache */ |
2810 | 16 | LatValue *cached = (LatValue *)lat_map_get(&ev->loaded_extensions, ext_name); |
2811 | 16 | if (cached) { |
2812 | 0 | LatValue result = value_deep_clone(cached); |
2813 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2814 | 0 | free(args); |
2815 | 0 | return eval_ok(result); |
2816 | 0 | } |
2817 | 16 | char *ext_err = NULL; |
2818 | 16 | LatValue ext_map = ext_load(ev, ext_name, &ext_err); |
2819 | 16 | if (ext_err) { |
2820 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2821 | 1 | free(args); |
2822 | 1 | return eval_err(ext_err); |
2823 | 1 | } |
2824 | | /* Cache the extension */ |
2825 | 15 | LatValue cached_copy = value_deep_clone(&ext_map); |
2826 | 15 | lat_map_set(&ev->loaded_extensions, ext_name, &cached_copy); |
2827 | 30 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2828 | 15 | free(args); |
2829 | 15 | return eval_ok(ext_map); |
2830 | 16 | } |
2831 | | |
2832 | | /// @builtin lat_eval(source: String) -> Any |
2833 | | /// @category Metaprogramming |
2834 | | /// Parse and execute a string as Lattice source code, returning the result. |
2835 | | /// @example lat_eval("1 + 2") // 3 |
2836 | 1.91k | if (strcmp(fn_name, "lat_eval") == 0) { |
2837 | 13 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("lat_eval() expects 1 string argument")); } |
2838 | 13 | const char *source = args[0].as.str_val; |
2839 | 13 | Lexer lex = lexer_new(source); |
2840 | 13 | char *lex_err = NULL; |
2841 | 13 | LatVec toks = lexer_tokenize(&lex, &lex_err); |
2842 | 13 | if (lex_err) { |
2843 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2844 | 0 | free(args); |
2845 | 0 | return eval_err(lex_err); |
2846 | 0 | } |
2847 | 13 | Parser parser = parser_new(&toks); |
2848 | 13 | char *parse_err = NULL; |
2849 | 13 | Program prog = parser_parse(&parser, &parse_err); |
2850 | 13 | if (parse_err) { |
2851 | 0 | program_free(&prog); |
2852 | 0 | for (size_t j = 0; j < toks.len; j++) token_free(lat_vec_get(&toks, j)); |
2853 | 0 | lat_vec_free(&toks); |
2854 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2855 | 0 | free(args); |
2856 | 0 | return eval_err(parse_err); |
2857 | 0 | } |
2858 | | /* Register functions, structs, traits, impls (same as evaluator_run) */ |
2859 | 26 | for (size_t j = 0; j < prog.item_count; j++) { |
2860 | 13 | if (prog.items[j].tag == ITEM_STRUCT) { |
2861 | 1 | StructDecl *ptr = &prog.items[j].as.struct_decl; |
2862 | 1 | lat_map_set(&ev->struct_defs, ptr->name, &ptr); |
2863 | 12 | } else if (prog.items[j].tag == ITEM_FUNCTION) { |
2864 | 1 | FnDecl *ptr = &prog.items[j].as.fn_decl; |
2865 | 1 | register_fn_overload(&ev->fn_defs, ptr); |
2866 | 11 | } else if (prog.items[j].tag == ITEM_TRAIT) { |
2867 | 0 | TraitDecl *ptr = &prog.items[j].as.trait_decl; |
2868 | 0 | lat_map_set(&ev->trait_defs, ptr->name, &ptr); |
2869 | 11 | } else if (prog.items[j].tag == ITEM_IMPL) { |
2870 | 0 | ImplBlock *ptr = &prog.items[j].as.impl_block; |
2871 | 0 | char key[512]; |
2872 | 0 | snprintf(key, sizeof(key), "%s::%s", ptr->type_name, ptr->trait_name); |
2873 | 0 | lat_map_set(&ev->impl_registry, key, &ptr); |
2874 | 0 | } |
2875 | 13 | } |
2876 | | /* Execute statements — set lat_eval_scope so top-level |
2877 | | * bindings persist in the caller's scope */ |
2878 | 13 | size_t saved_scope = ev->lat_eval_scope; |
2879 | 13 | ev->lat_eval_scope = ev->env->count; |
2880 | 13 | EvalResult eval_r = eval_ok(value_unit()); |
2881 | 26 | for (size_t j = 0; j < prog.item_count; j++) { |
2882 | 13 | if (prog.items[j].tag == ITEM_STMT) { |
2883 | 11 | value_free(&eval_r.value); |
2884 | 11 | eval_r = eval_stmt(ev, prog.items[j].as.stmt); |
2885 | 11 | if (!IS_OK(eval_r)) break; |
2886 | 11 | } |
2887 | 13 | } |
2888 | 13 | ev->lat_eval_scope = saved_scope; |
2889 | | /* Free statement items. If fn/struct decls were registered, |
2890 | | * keep the items array alive (decls live inline in it). |
2891 | | * Otherwise free the whole program. */ |
2892 | 13 | bool has_decls = false; |
2893 | 26 | for (size_t j = 0; j < prog.item_count; j++) { |
2894 | 13 | if (prog.items[j].tag == ITEM_STMT) |
2895 | 11 | stmt_free(prog.items[j].as.stmt); |
2896 | 2 | else |
2897 | 2 | has_decls = true; |
2898 | 13 | } |
2899 | 13 | if (!has_decls) free(prog.items); |
2900 | 104 | for (size_t j = 0; j < toks.len; j++) token_free(lat_vec_get(&toks, j)); |
2901 | 13 | lat_vec_free(&toks); |
2902 | 26 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2903 | 13 | free(args); |
2904 | 13 | return eval_r; |
2905 | 13 | } |
2906 | | |
2907 | | /// @builtin tokenize(source: String) -> Array |
2908 | | /// @category Metaprogramming |
2909 | | /// Tokenize a source string, returning an array of Token structs. |
2910 | | /// @example tokenize("1 + 2") // [{type: "INT_LIT", text: "1"}, ...] |
2911 | 1.89k | if (strcmp(fn_name, "tokenize") == 0) { |
2912 | 1 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tokenize() expects 1 string argument")); } |
2913 | 1 | const char *source = args[0].as.str_val; |
2914 | 1 | Lexer lex = lexer_new(source); |
2915 | 1 | char *lex_err = NULL; |
2916 | 1 | LatVec toks = lexer_tokenize(&lex, &lex_err); |
2917 | 1 | if (lex_err) { |
2918 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2919 | 0 | free(args); |
2920 | 0 | return eval_err(lex_err); |
2921 | 0 | } |
2922 | 1 | size_t tok_count = toks.len > 0 ? toks.len - 1 : 0; |
2923 | 1 | LatValue *elems = malloc((tok_count > 0 ? tok_count : 1) * sizeof(LatValue)); |
2924 | 5 | for (size_t j = 0; j < tok_count; j++) { |
2925 | 4 | Token *t = lat_vec_get(&toks, j); |
2926 | 4 | const char *type_str = token_type_name(t->type); |
2927 | 4 | char *text; |
2928 | 4 | if (t->type == TOK_IDENT || t->type == TOK_STRING_LIT || t->type == TOK_MODE_DIRECTIVE) { |
2929 | 1 | text = strdup(t->as.str_val); |
2930 | 3 | } else if (t->type == TOK_INT_LIT) { |
2931 | 1 | (void)asprintf(&text, "%lld", (long long)t->as.int_val); |
2932 | 2 | } else if (t->type == TOK_FLOAT_LIT) { |
2933 | 0 | (void)asprintf(&text, "%g", t->as.float_val); |
2934 | 2 | } else { |
2935 | 2 | text = strdup(token_type_name(t->type)); |
2936 | 2 | } |
2937 | 4 | char *fnames[2] = { "type", "text" }; |
2938 | 4 | LatValue fvals[2]; |
2939 | 4 | fvals[0] = value_string(type_str); |
2940 | 4 | fvals[1] = value_string_owned(text); |
2941 | 4 | elems[j] = value_struct("Token", fnames, fvals, 2); |
2942 | 4 | } |
2943 | 6 | for (size_t j = 0; j < toks.len; j++) token_free(lat_vec_get(&toks, j)); |
2944 | 1 | lat_vec_free(&toks); |
2945 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2946 | 1 | free(args); |
2947 | 1 | LatValue arr = value_array(elems, tok_count); |
2948 | 1 | free(elems); |
2949 | 1 | return eval_ok(arr); |
2950 | 1 | } |
2951 | | |
2952 | | /// @builtin Map::new() -> Map |
2953 | | /// @category Type Constructors |
2954 | | /// Create a new empty map. |
2955 | | /// @example Map::new() // {} |
2956 | 1.89k | if (strcmp(fn_name, "Map::new") == 0) { |
2957 | 411 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2958 | 411 | free(args); |
2959 | 411 | return eval_ok(value_map_new()); |
2960 | 411 | } |
2961 | | |
2962 | | /// @builtin Channel::new() -> Channel |
2963 | | /// @category Type Constructors |
2964 | | /// Create a new channel for concurrent communication. |
2965 | | /// @example Channel::new() // <Channel> |
2966 | 1.48k | if (strcmp(fn_name, "Channel::new") == 0) { |
2967 | 14 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2968 | 14 | free(args); |
2969 | 14 | LatChannel *ch = channel_new(); |
2970 | 14 | LatValue val = value_channel(ch); |
2971 | 14 | channel_release(ch); /* value_channel retained; drop our creation ref */ |
2972 | 14 | return eval_ok(val); |
2973 | 14 | } |
2974 | | |
2975 | | /// @builtin Set::new() -> Set |
2976 | | /// @category Type Constructors |
2977 | | /// Create a new empty set. |
2978 | | /// @example Set::new() // Set{} |
2979 | 1.47k | if (strcmp(fn_name, "Set::new") == 0) { |
2980 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2981 | 4 | free(args); |
2982 | 4 | return eval_ok(value_set_new()); |
2983 | 4 | } |
2984 | | |
2985 | | /// @builtin Set::from(array: Array) -> Set |
2986 | | /// @category Type Constructors |
2987 | | /// Create a set from an array (duplicates removed). |
2988 | | /// @example Set::from([1, 2, 2, 3]) // Set{1, 2, 3} |
2989 | 1.46k | if (strcmp(fn_name, "Set::from") == 0) { |
2990 | 12 | if (argc != 1 || args[0].type != VAL_ARRAY) { |
2991 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
2992 | 0 | free(args); |
2993 | 0 | return eval_err(strdup("Set::from() expects 1 array argument")); |
2994 | 0 | } |
2995 | 12 | LatValue set = value_set_new(); |
2996 | 42 | for (size_t i = 0; i < args[0].as.array.len; i++) { |
2997 | 30 | LatValue *elem = &args[0].as.array.elems[i]; |
2998 | 30 | char *key = value_display(elem); |
2999 | 30 | LatValue cloned = value_deep_clone(elem); |
3000 | 30 | lat_map_set(set.as.set.map, key, &cloned); |
3001 | 30 | free(key); |
3002 | 30 | } |
3003 | 24 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3004 | 12 | free(args); |
3005 | 12 | return eval_ok(set); |
3006 | 12 | } |
3007 | | |
3008 | | /// @builtin Buffer::new(size: Int) -> Buffer |
3009 | | /// @category Type Constructors |
3010 | | /// Create a new zero-filled buffer of the given size. |
3011 | | /// @example Buffer::new(16) // Buffer<16 bytes> |
3012 | 1.45k | if (strcmp(fn_name, "Buffer::new") == 0) { |
3013 | 8 | if (argc != 1 || args[0].type != VAL_INT) { |
3014 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3015 | 0 | free(args); |
3016 | 0 | return eval_err(strdup("Buffer::new() expects 1 Int argument")); |
3017 | 0 | } |
3018 | 8 | int64_t size = args[0].as.int_val; |
3019 | 16 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3020 | 8 | free(args); |
3021 | 8 | return eval_ok(value_buffer_alloc(size < 0 ? 0 : (size_t)size)); |
3022 | 8 | } |
3023 | | |
3024 | | /// @builtin Buffer::from(arr: Array) -> Buffer |
3025 | | /// @category Type Constructors |
3026 | | /// Create a buffer from an array of byte integers (0-255). |
3027 | | /// @example Buffer::from([0xFF, 0x00, 0x42]) |
3028 | 1.44k | if (strcmp(fn_name, "Buffer::from") == 0) { |
3029 | 8 | if (argc != 1 || args[0].type != VAL_ARRAY) { |
3030 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3031 | 0 | free(args); |
3032 | 0 | return eval_err(strdup("Buffer::from() expects 1 Array argument")); |
3033 | 0 | } |
3034 | 8 | size_t blen = args[0].as.array.len; |
3035 | 8 | uint8_t *data = malloc(blen > 0 ? blen : 1); |
3036 | 34 | for (size_t bi = 0; bi < blen; bi++) { |
3037 | 26 | if (args[0].as.array.elems[bi].type == VAL_INT) |
3038 | 26 | data[bi] = (uint8_t)(args[0].as.array.elems[bi].as.int_val & 0xFF); |
3039 | 0 | else |
3040 | 0 | data[bi] = 0; |
3041 | 26 | } |
3042 | 8 | LatValue buf = value_buffer(data, blen); |
3043 | 8 | free(data); |
3044 | 16 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3045 | 8 | free(args); |
3046 | 8 | return eval_ok(buf); |
3047 | 8 | } |
3048 | | |
3049 | | /// @builtin Buffer::from_string(s: String) -> Buffer |
3050 | | /// @category Type Constructors |
3051 | | /// Create a buffer from a UTF-8 string. |
3052 | | /// @example Buffer::from_string("hello") |
3053 | 1.44k | if (strcmp(fn_name, "Buffer::from_string") == 0) { |
3054 | 2 | if (argc != 1 || args[0].type != VAL_STR) { |
3055 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3056 | 0 | free(args); |
3057 | 0 | return eval_err(strdup("Buffer::from_string() expects 1 String argument")); |
3058 | 0 | } |
3059 | 2 | const char *s = args[0].as.str_val; |
3060 | 2 | size_t slen = strlen(s); |
3061 | 2 | LatValue buf = value_buffer((const uint8_t *)s, slen); |
3062 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3063 | 2 | free(args); |
3064 | 2 | return eval_ok(buf); |
3065 | 2 | } |
3066 | | |
3067 | | /// @builtin Ref::new(value: Any) -> Ref |
3068 | | /// @category Type Constructors |
3069 | | /// Create a new reference-counted shared wrapper around a value. |
3070 | | /// @example Ref::new({}) |
3071 | 1.43k | if (strcmp(fn_name, "Ref::new") == 0) { |
3072 | 15 | if (argc != 1) { |
3073 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3074 | 0 | free(args); |
3075 | 0 | return eval_err(strdup("Ref::new() expects 1 argument")); |
3076 | 0 | } |
3077 | 15 | LatValue ref = value_ref(args[0]); |
3078 | 30 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3079 | 15 | free(args); |
3080 | 15 | return eval_ok(ref); |
3081 | 15 | } |
3082 | | |
3083 | | /// @builtin parse_int(s: String) -> Int |
3084 | | /// @category Type Conversion |
3085 | | /// Parse a string as an integer. |
3086 | | /// @example parse_int("42") // 42 |
3087 | 1.42k | if (strcmp(fn_name, "parse_int") == 0) { |
3088 | 2 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("parse_int() expects 1 string argument")); } |
3089 | 2 | bool ok; |
3090 | 2 | int64_t val = builtin_parse_int(args[0].as.str_val, &ok); |
3091 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3092 | 2 | free(args); |
3093 | 2 | if (!ok) return eval_err(strdup("parse_int: invalid integer")); |
3094 | 2 | return eval_ok(value_int(val)); |
3095 | 2 | } |
3096 | | |
3097 | | /// @builtin parse_float(s: String) -> Float |
3098 | | /// @category Type Conversion |
3099 | | /// Parse a string as a floating-point number. |
3100 | | /// @example parse_float("3.14") // 3.14 |
3101 | 1.42k | if (strcmp(fn_name, "parse_float") == 0) { |
3102 | 1 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("parse_float() expects 1 string argument")); } |
3103 | 1 | bool ok; |
3104 | 1 | double val = builtin_parse_float(args[0].as.str_val, &ok); |
3105 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3106 | 1 | free(args); |
3107 | 1 | if (!ok) return eval_err(strdup("parse_float: invalid float")); |
3108 | 1 | return eval_ok(value_float(val)); |
3109 | 1 | } |
3110 | | |
3111 | | /// @builtin error(msg: String) -> String |
3112 | | /// @category Error Handling |
3113 | | /// Create an error value with the given message. |
3114 | | /// @example error("something went wrong") // "EVAL_ERROR:something went wrong" |
3115 | 1.42k | if (strcmp(fn_name, "error") == 0) { |
3116 | 1 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("error() expects 1 string argument")); } |
3117 | 1 | char *msg = NULL; |
3118 | 1 | (void)asprintf(&msg, "EVAL_ERROR:%s", args[0].as.str_val); |
3119 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3120 | 1 | free(args); |
3121 | 1 | return eval_ok(value_string_owned(msg)); |
3122 | 1 | } |
3123 | | |
3124 | | /// @builtin panic(msg: String) -> Unit |
3125 | | /// @category Error Handling |
3126 | | /// Trigger an immediate fatal error that cannot be caught by try/catch. |
3127 | | /// @example panic("unrecoverable state") |
3128 | 1.42k | if (strcmp(fn_name, "panic") == 0) { |
3129 | 1 | const char *msg = (argc >= 1 && args[0].type == VAL_STR) ? args[0].as.str_val : "panic"; |
3130 | 1 | char *err = strdup(msg); |
3131 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3132 | 1 | free(args); |
3133 | 1 | return eval_err(err); |
3134 | 1 | } |
3135 | | |
3136 | | /// @builtin is_error(val: Any) -> Bool |
3137 | | /// @category Error Handling |
3138 | | /// Check if a value is an error value. |
3139 | | /// @example is_error(error("oops")) // true |
3140 | 1.41k | if (strcmp(fn_name, "is_error") == 0) { |
3141 | 3 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("is_error() expects 1 argument")); } |
3142 | 3 | bool is_err = args[0].type == VAL_STR && strncmp(args[0].as.str_val, "EVAL_ERROR:", 11) == 0; |
3143 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3144 | 3 | free(args); |
3145 | 3 | return eval_ok(value_bool(is_err)); |
3146 | 3 | } |
3147 | | |
3148 | | /// @builtin len(val: String|Array|Map) -> Int |
3149 | | /// @category Core |
3150 | | /// Returns the length of a string, array, or map. |
3151 | | /// @example len("hello") // 5 |
3152 | | /// @example len([1, 2, 3]) // 3 |
3153 | 1.41k | if (strcmp(fn_name, "len") == 0) { |
3154 | 243 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("len() expects 1 argument")); } |
3155 | 243 | int64_t l = -1; |
3156 | 243 | if (args[0].type == VAL_STR) l = (int64_t)strlen(args[0].as.str_val); |
3157 | 168 | else if (args[0].type == VAL_ARRAY) l = (int64_t)args[0].as.array.len; |
3158 | 2 | else if (args[0].type == VAL_MAP) l = (int64_t)lat_map_len(args[0].as.map.map); |
3159 | 1 | else if (args[0].type == VAL_SET) l = (int64_t)lat_map_len(args[0].as.set.map); |
3160 | 1 | else if (args[0].type == VAL_BUFFER) l = (int64_t)args[0].as.buffer.len; |
3161 | 486 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3162 | 243 | free(args); |
3163 | 243 | if (l < 0) return eval_err(strdup("len() not supported on this type")); |
3164 | 243 | return eval_ok(value_int(l)); |
3165 | 243 | } |
3166 | | |
3167 | | /// @builtin exit(code?: Int) -> Unit |
3168 | | /// @category Core |
3169 | | /// Exit the program with an optional exit code (default 0). |
3170 | | /// @example exit(1) // exits with code 1 |
3171 | 1.17k | if (strcmp(fn_name, "exit") == 0) { |
3172 | 0 | int code = 0; |
3173 | 0 | if (argc > 0 && args[0].type == VAL_INT) code = (int)args[0].as.int_val; |
3174 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3175 | 0 | free(args); |
3176 | 0 | exit(code); |
3177 | 0 | } |
3178 | | |
3179 | | /// @builtin version() -> String |
3180 | | /// @category Core |
3181 | | /// Return the Lattice interpreter version string. |
3182 | | /// @example version() // "0.1.0" |
3183 | 1.17k | if (strcmp(fn_name, "version") == 0) { |
3184 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3185 | 2 | free(args); |
3186 | 2 | return eval_ok(value_string(LATTICE_VERSION)); |
3187 | 2 | } |
3188 | | |
3189 | | /// @builtin print_raw(args: Any...) -> Unit |
3190 | | /// @category Core |
3191 | | /// Print values separated by spaces without a trailing newline. |
3192 | | /// @example print_raw("hello", "world") // prints: hello world |
3193 | 1.17k | if (strcmp(fn_name, "print_raw") == 0) { |
3194 | 4 | for (size_t i = 0; i < argc; i++) { |
3195 | 2 | if (i > 0) printf(" "); |
3196 | 2 | char *s = value_display(&args[i]); |
3197 | 2 | printf("%s", s); |
3198 | 2 | free(s); |
3199 | 2 | } |
3200 | 2 | fflush(stdout); |
3201 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3202 | 2 | free(args); |
3203 | 2 | return eval_ok(value_unit()); |
3204 | 2 | } |
3205 | | |
3206 | | /// @builtin eprint(args: Any...) -> Unit |
3207 | | /// @category Core |
3208 | | /// Print values to stderr with a trailing newline. |
3209 | | /// @example eprint("warning:", msg) // prints to stderr |
3210 | 1.16k | if (strcmp(fn_name, "eprint") == 0) { |
3211 | 2 | for (size_t i = 0; i < argc; i++) { |
3212 | 1 | if (i > 0) fprintf(stderr, " "); |
3213 | 1 | char *s = value_display(&args[i]); |
3214 | 1 | fprintf(stderr, "%s", s); |
3215 | 1 | free(s); |
3216 | 1 | } |
3217 | 1 | fprintf(stderr, "\n"); |
3218 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3219 | 1 | free(args); |
3220 | 1 | return eval_ok(value_unit()); |
3221 | 1 | } |
3222 | | |
3223 | | /* ── TCP networking builtins ── */ |
3224 | | |
3225 | | /// @builtin tcp_listen(host: String, port: Int) -> Int |
3226 | | /// @category Networking |
3227 | | /// Create a TCP server socket listening on host:port, returning a file descriptor. |
3228 | | /// @example tcp_listen("0.0.0.0", 8080) // 3 |
3229 | 1.16k | if (strcmp(fn_name, "tcp_listen") == 0) { |
3230 | 3 | if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_listen() expects (String host, Int port)")); } |
3231 | 1 | char *net_err = NULL; |
3232 | 1 | int fd = net_tcp_listen(args[0].as.str_val, (int)args[1].as.int_val, &net_err); |
3233 | 3 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3234 | 1 | free(args); |
3235 | 1 | if (fd < 0) return eval_err(net_err); |
3236 | 1 | return eval_ok(value_int(fd)); |
3237 | 1 | } |
3238 | | |
3239 | | /// @builtin tcp_accept(server_fd: Int) -> Int |
3240 | | /// @category Networking |
3241 | | /// Accept an incoming TCP connection, returning a new client file descriptor. |
3242 | | /// @example tcp_accept(server_fd) // 4 |
3243 | 1.16k | if (strcmp(fn_name, "tcp_accept") == 0) { |
3244 | 0 | if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_accept() expects (Int server_fd)")); } |
3245 | 0 | char *net_err = NULL; |
3246 | 0 | int fd = net_tcp_accept((int)args[0].as.int_val, &net_err); |
3247 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3248 | 0 | free(args); |
3249 | 0 | if (fd < 0) return eval_err(net_err); |
3250 | 0 | return eval_ok(value_int(fd)); |
3251 | 0 | } |
3252 | | |
3253 | | /// @builtin tcp_connect(host: String, port: Int) -> Int |
3254 | | /// @category Networking |
3255 | | /// Connect to a TCP server, returning a file descriptor. |
3256 | | /// @example tcp_connect("localhost", 8080) // 3 |
3257 | 1.16k | if (strcmp(fn_name, "tcp_connect") == 0) { |
3258 | 0 | if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_connect() expects (String host, Int port)")); } |
3259 | 0 | char *net_err = NULL; |
3260 | 0 | int fd = net_tcp_connect(args[0].as.str_val, (int)args[1].as.int_val, &net_err); |
3261 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3262 | 0 | free(args); |
3263 | 0 | if (fd < 0) return eval_err(net_err); |
3264 | 0 | return eval_ok(value_int(fd)); |
3265 | 0 | } |
3266 | | |
3267 | | /// @builtin tcp_read(fd: Int) -> String |
3268 | | /// @category Networking |
3269 | | /// Read data from a TCP socket as a string. |
3270 | | /// @example tcp_read(client_fd) // "HTTP/1.1 200 OK..." |
3271 | 1.16k | if (strcmp(fn_name, "tcp_read") == 0) { |
3272 | 2 | if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_read() expects (Int fd)")); } |
3273 | 0 | char *net_err = NULL; |
3274 | 0 | char *data = net_tcp_read((int)args[0].as.int_val, &net_err); |
3275 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3276 | 0 | free(args); |
3277 | 0 | if (!data) return eval_err(net_err); |
3278 | 0 | return eval_ok(value_string_owned(data)); |
3279 | 0 | } |
3280 | | |
3281 | | /// @builtin tcp_read_bytes(fd: Int, n: Int) -> String |
3282 | | /// @category Networking |
3283 | | /// Read exactly n bytes from a TCP socket. |
3284 | | /// @example tcp_read_bytes(fd, 1024) // "..." |
3285 | 1.16k | if (strcmp(fn_name, "tcp_read_bytes") == 0) { |
3286 | 0 | if (argc != 2 || args[0].type != VAL_INT || args[1].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_read_bytes() expects (Int fd, Int n)")); } |
3287 | 0 | char *net_err = NULL; |
3288 | 0 | char *data = net_tcp_read_bytes((int)args[0].as.int_val, (size_t)args[1].as.int_val, &net_err); |
3289 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3290 | 0 | free(args); |
3291 | 0 | if (!data) return eval_err(net_err); |
3292 | 0 | return eval_ok(value_string_owned(data)); |
3293 | 0 | } |
3294 | | |
3295 | | /// @builtin tcp_write(fd: Int, data: String) -> Bool |
3296 | | /// @category Networking |
3297 | | /// Write a string to a TCP socket. |
3298 | | /// @example tcp_write(fd, "GET / HTTP/1.1\r\n\r\n") // true |
3299 | 1.16k | if (strcmp(fn_name, "tcp_write") == 0) { |
3300 | 0 | if (argc != 2 || args[0].type != VAL_INT || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_write() expects (Int fd, String data)")); } |
3301 | 0 | char *net_err = NULL; |
3302 | 0 | bool ok = net_tcp_write((int)args[0].as.int_val, args[1].as.str_val, strlen(args[1].as.str_val), &net_err); |
3303 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3304 | 0 | free(args); |
3305 | 0 | if (!ok) return eval_err(net_err); |
3306 | 0 | return eval_ok(value_bool(true)); |
3307 | 0 | } |
3308 | | |
3309 | | /// @builtin tcp_close(fd: Int) -> Unit |
3310 | | /// @category Networking |
3311 | | /// Close a TCP socket. |
3312 | | /// @example tcp_close(fd) |
3313 | 1.16k | if (strcmp(fn_name, "tcp_close") == 0) { |
3314 | 1 | if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_close() expects (Int fd)")); } |
3315 | 1 | net_tcp_close((int)args[0].as.int_val); |
3316 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3317 | 1 | free(args); |
3318 | 1 | return eval_ok(value_unit()); |
3319 | 1 | } |
3320 | | |
3321 | | /// @builtin tcp_peer_addr(fd: Int) -> String |
3322 | | /// @category Networking |
3323 | | /// Get the remote address of a connected TCP socket. |
3324 | | /// @example tcp_peer_addr(client_fd) // "192.168.1.1:54321" |
3325 | 1.16k | if (strcmp(fn_name, "tcp_peer_addr") == 0) { |
3326 | 0 | if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_peer_addr() expects (Int fd)")); } |
3327 | 0 | char *net_err = NULL; |
3328 | 0 | char *addr = net_tcp_peer_addr((int)args[0].as.int_val, &net_err); |
3329 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3330 | 0 | free(args); |
3331 | 0 | if (!addr) return eval_err(net_err); |
3332 | 0 | return eval_ok(value_string_owned(addr)); |
3333 | 0 | } |
3334 | | |
3335 | | /// @builtin tcp_set_timeout(fd: Int, secs: Int) -> Bool |
3336 | | /// @category Networking |
3337 | | /// Set read/write timeout on a TCP socket in seconds. |
3338 | | /// @example tcp_set_timeout(fd, 30) // true |
3339 | 1.16k | if (strcmp(fn_name, "tcp_set_timeout") == 0) { |
3340 | 0 | if (argc != 2 || args[0].type != VAL_INT || args[1].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_set_timeout() expects (Int fd, Int secs)")); } |
3341 | 0 | char *net_err = NULL; |
3342 | 0 | bool ok = net_tcp_set_timeout((int)args[0].as.int_val, (int)args[1].as.int_val, &net_err); |
3343 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3344 | 0 | free(args); |
3345 | 0 | if (!ok) return eval_err(net_err); |
3346 | 0 | return eval_ok(value_bool(true)); |
3347 | 0 | } |
3348 | | |
3349 | | /* ── TLS networking builtins ── */ |
3350 | | |
3351 | | /// @builtin tls_connect(host: String, port: Int) -> Int |
3352 | | /// @category Networking |
3353 | | /// Establish a TLS connection to a server, returning a handle. |
3354 | | /// @example tls_connect("example.com", 443) // 1 |
3355 | 1.16k | if (strcmp(fn_name, "tls_connect") == 0) { |
3356 | 3 | if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tls_connect() expects (String host, Int port)")); } |
3357 | 0 | char *net_err = NULL; |
3358 | 0 | int fd = net_tls_connect(args[0].as.str_val, (int)args[1].as.int_val, &net_err); |
3359 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3360 | 0 | free(args); |
3361 | 0 | if (fd < 0) return eval_err(net_err); |
3362 | 0 | return eval_ok(value_int(fd)); |
3363 | 0 | } |
3364 | | |
3365 | | /// @builtin tls_read(handle: Int) -> String |
3366 | | /// @category Networking |
3367 | | /// Read data from a TLS connection as a string. |
3368 | | /// @example tls_read(handle) // "HTTP/1.1 200 OK..." |
3369 | 1.16k | if (strcmp(fn_name, "tls_read") == 0) { |
3370 | 2 | if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tls_read() expects (Int fd)")); } |
3371 | 0 | char *net_err = NULL; |
3372 | 0 | char *data = net_tls_read((int)args[0].as.int_val, &net_err); |
3373 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3374 | 0 | free(args); |
3375 | 0 | if (!data) return eval_err(net_err); |
3376 | 0 | return eval_ok(value_string_owned(data)); |
3377 | 0 | } |
3378 | | |
3379 | | /// @builtin tls_read_bytes(handle: Int, n: Int) -> String |
3380 | | /// @category Networking |
3381 | | /// Read exactly n bytes from a TLS connection. |
3382 | | /// @example tls_read_bytes(handle, 512) // "..." |
3383 | 1.16k | if (strcmp(fn_name, "tls_read_bytes") == 0) { |
3384 | 0 | if (argc != 2 || args[0].type != VAL_INT || args[1].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tls_read_bytes() expects (Int fd, Int n)")); } |
3385 | 0 | char *net_err = NULL; |
3386 | 0 | char *data = net_tls_read_bytes((int)args[0].as.int_val, (size_t)args[1].as.int_val, &net_err); |
3387 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3388 | 0 | free(args); |
3389 | 0 | if (!data) return eval_err(net_err); |
3390 | 0 | return eval_ok(value_string_owned(data)); |
3391 | 0 | } |
3392 | | |
3393 | | /// @builtin tls_write(handle: Int, data: String) -> Bool |
3394 | | /// @category Networking |
3395 | | /// Write a string to a TLS connection. |
3396 | | /// @example tls_write(handle, "GET / HTTP/1.1\r\n\r\n") // true |
3397 | 1.16k | if (strcmp(fn_name, "tls_write") == 0) { |
3398 | 0 | if (argc != 2 || args[0].type != VAL_INT || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tls_write() expects (Int fd, String data)")); } |
3399 | 0 | char *net_err = NULL; |
3400 | 0 | bool ok = net_tls_write((int)args[0].as.int_val, args[1].as.str_val, strlen(args[1].as.str_val), &net_err); |
3401 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3402 | 0 | free(args); |
3403 | 0 | if (!ok) return eval_err(net_err); |
3404 | 0 | return eval_ok(value_bool(true)); |
3405 | 0 | } |
3406 | | |
3407 | | /// @builtin tls_close(handle: Int) -> Unit |
3408 | | /// @category Networking |
3409 | | /// Close a TLS connection. |
3410 | | /// @example tls_close(handle) |
3411 | 1.16k | if (strcmp(fn_name, "tls_close") == 0) { |
3412 | 0 | if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tls_close() expects (Int fd)")); } |
3413 | 0 | net_tls_close((int)args[0].as.int_val); |
3414 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3415 | 0 | free(args); |
3416 | 0 | return eval_ok(value_unit()); |
3417 | 0 | } |
3418 | | |
3419 | | /// @builtin tls_available() -> Bool |
3420 | | /// @category Networking |
3421 | | /// Check if TLS support is available (OpenSSL linked). |
3422 | | /// @example tls_available() // true |
3423 | 1.16k | if (strcmp(fn_name, "tls_available") == 0) { |
3424 | 1 | if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tls_available() expects no arguments")); } |
3425 | 1 | free(args); |
3426 | 1 | return eval_ok(value_bool(net_tls_available())); |
3427 | 1 | } |
3428 | | |
3429 | | /* ── JSON builtins ── */ |
3430 | | |
3431 | | /// @builtin json_parse(s: String) -> Any |
3432 | | /// @category JSON |
3433 | | /// Parse a JSON string into a Lattice value. |
3434 | | /// @example json_parse("{\"a\": 1}") // {a: 1} |
3435 | 1.16k | if (strcmp(fn_name, "json_parse") == 0) { |
3436 | 15 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("json_parse() expects (String)")); } |
3437 | 15 | char *jerr = NULL; |
3438 | 15 | LatValue result = json_parse(args[0].as.str_val, &jerr); |
3439 | 30 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3440 | 15 | free(args); |
3441 | 15 | if (jerr) return eval_err(jerr); |
3442 | 14 | return eval_ok(result); |
3443 | 15 | } |
3444 | | |
3445 | | /// @builtin json_stringify(val: Any) -> String |
3446 | | /// @category JSON |
3447 | | /// Serialize a Lattice value to a JSON string. |
3448 | | /// @example json_stringify([1, 2, 3]) // "[1,2,3]" |
3449 | 1.14k | if (strcmp(fn_name, "json_stringify") == 0) { |
3450 | 8 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("json_stringify() expects (value)")); } |
3451 | 7 | char *jerr = NULL; |
3452 | 7 | char *json = json_stringify(&args[0], &jerr); |
3453 | 14 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3454 | 7 | free(args); |
3455 | 7 | if (!json) return eval_err(jerr); |
3456 | 7 | return eval_ok(value_string_owned(json)); |
3457 | 7 | } |
3458 | | |
3459 | | /* ── HTTP builtins ── */ |
3460 | | |
3461 | | /// @builtin http_get(url: String) -> Map |
3462 | | /// @category HTTP |
3463 | | /// Perform an HTTP GET request. Returns a map with "status", "headers", and "body". |
3464 | | /// @example http_get("https://httpbin.org/get") |
3465 | 1.13k | if (strcmp(fn_name, "http_get") == 0) { |
3466 | 3 | if (argc != 1 || args[0].type != VAL_STR) { |
3467 | 3 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
3468 | 2 | return eval_err(strdup("http_get() expects (url: String)")); |
3469 | 2 | } |
3470 | 1 | HttpRequest hreq = { |
3471 | 1 | .method = "GET", .url = args[0].as.str_val, |
3472 | 1 | .header_keys = NULL, .header_values = NULL, .header_count = 0, |
3473 | 1 | .body = NULL, .body_len = 0, .timeout_ms = 0 |
3474 | 1 | }; |
3475 | 1 | char *herr = NULL; |
3476 | 1 | HttpResponse *hresp = http_execute(&hreq, &herr); |
3477 | 2 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
3478 | 1 | if (!hresp) return eval_err(herr ? herr : strdup("http_get failed")); |
3479 | | /* Build result map */ |
3480 | 0 | LatValue result = value_map_new(); |
3481 | 0 | LatValue st = value_int(hresp->status_code); |
3482 | 0 | lat_map_set(result.as.map.map, "status", &st); |
3483 | 0 | LatValue bd = value_string(hresp->body ? hresp->body : ""); |
3484 | 0 | lat_map_set(result.as.map.map, "body", &bd); |
3485 | 0 | LatValue hdrs = value_map_new(); |
3486 | 0 | for (size_t i = 0; i < hresp->header_count; i++) { |
3487 | 0 | LatValue hv = value_string(hresp->header_values[i]); |
3488 | 0 | lat_map_set(hdrs.as.map.map, hresp->header_keys[i], &hv); |
3489 | 0 | } |
3490 | 0 | lat_map_set(result.as.map.map, "headers", &hdrs); |
3491 | 0 | http_response_free(hresp); |
3492 | 0 | return eval_ok(result); |
3493 | 1 | } |
3494 | | |
3495 | | /// @builtin http_post(url: String, options: Map) -> Map |
3496 | | /// @category HTTP |
3497 | | /// Perform an HTTP POST request. Options map may contain "headers" (Map), "body" (String), and "timeout" (Int ms). |
3498 | | /// @example http_post("https://httpbin.org/post", {"body": "hello"}) |
3499 | 1.13k | if (strcmp(fn_name, "http_post") == 0) { |
3500 | 2 | if (argc < 1 || argc > 2 || args[0].type != VAL_STR) { |
3501 | 2 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
3502 | 1 | return eval_err(strdup("http_post() expects (url: String, options?: Map)")); |
3503 | 1 | } |
3504 | | /* Extract options */ |
3505 | 1 | const char *body_str = NULL; |
3506 | 1 | size_t body_len = 0; |
3507 | 1 | int timeout_ms = 0; |
3508 | 1 | char **hdr_keys = NULL, **hdr_vals = NULL; |
3509 | 1 | size_t hdr_count = 0; |
3510 | 1 | if (argc == 2 && args[1].type == VAL_MAP) { |
3511 | 0 | LatValue *bv = (LatValue *)lat_map_get(args[1].as.map.map, "body"); |
3512 | 0 | if (bv && bv->type == VAL_STR) { body_str = bv->as.str_val; body_len = strlen(body_str); } |
3513 | 0 | LatValue *tv = (LatValue *)lat_map_get(args[1].as.map.map, "timeout"); |
3514 | 0 | if (tv && tv->type == VAL_INT) timeout_ms = (int)tv->as.int_val; |
3515 | 0 | LatValue *hm = (LatValue *)lat_map_get(args[1].as.map.map, "headers"); |
3516 | 0 | if (hm && hm->type == VAL_MAP) { |
3517 | 0 | hdr_count = lat_map_len(hm->as.map.map); |
3518 | 0 | hdr_keys = malloc(hdr_count * sizeof(char *)); |
3519 | 0 | hdr_vals = malloc(hdr_count * sizeof(char *)); |
3520 | 0 | size_t hi = 0; |
3521 | 0 | for (size_t i = 0; i < hm->as.map.map->cap && hi < hdr_count; i++) { |
3522 | 0 | if (hm->as.map.map->entries[i].state == MAP_OCCUPIED) { |
3523 | 0 | hdr_keys[hi] = (char *)hm->as.map.map->entries[i].key; |
3524 | 0 | LatValue *v = (LatValue *)hm->as.map.map->entries[i].value; |
3525 | 0 | hdr_vals[hi] = v->type == VAL_STR ? v->as.str_val : ""; |
3526 | 0 | hi++; |
3527 | 0 | } |
3528 | 0 | } |
3529 | 0 | } |
3530 | 0 | } |
3531 | 1 | HttpRequest hreq = { |
3532 | 1 | .method = "POST", .url = args[0].as.str_val, |
3533 | 1 | .header_keys = hdr_keys, .header_values = hdr_vals, .header_count = hdr_count, |
3534 | 1 | .body = body_str, .body_len = body_len, .timeout_ms = timeout_ms |
3535 | 1 | }; |
3536 | 1 | char *herr = NULL; |
3537 | 1 | HttpResponse *hresp = http_execute(&hreq, &herr); |
3538 | 1 | free(hdr_keys); free(hdr_vals); |
3539 | 2 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
3540 | 1 | if (!hresp) return eval_err(herr ? herr : strdup("http_post failed")); |
3541 | 0 | LatValue result = value_map_new(); |
3542 | 0 | LatValue st = value_int(hresp->status_code); |
3543 | 0 | lat_map_set(result.as.map.map, "status", &st); |
3544 | 0 | LatValue bd = value_string(hresp->body ? hresp->body : ""); |
3545 | 0 | lat_map_set(result.as.map.map, "body", &bd); |
3546 | 0 | LatValue hdrs = value_map_new(); |
3547 | 0 | for (size_t i = 0; i < hresp->header_count; i++) { |
3548 | 0 | LatValue hv = value_string(hresp->header_values[i]); |
3549 | 0 | lat_map_set(hdrs.as.map.map, hresp->header_keys[i], &hv); |
3550 | 0 | } |
3551 | 0 | lat_map_set(result.as.map.map, "headers", &hdrs); |
3552 | 0 | http_response_free(hresp); |
3553 | 0 | return eval_ok(result); |
3554 | 1 | } |
3555 | | |
3556 | | /// @builtin http_request(method: String, url: String, options?: Map) -> Map |
3557 | | /// @category HTTP |
3558 | | /// Perform an HTTP request with a custom method. Options may contain "headers", "body", and "timeout". |
3559 | | /// @example http_request("PUT", "https://api.example.com/data", {"body": "{}"}) |
3560 | 1.13k | if (strcmp(fn_name, "http_request") == 0) { |
3561 | 3 | if (argc < 2 || argc > 3 || args[0].type != VAL_STR || args[1].type != VAL_STR) { |
3562 | 5 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
3563 | 2 | return eval_err(strdup("http_request() expects (method: String, url: String, options?: Map)")); |
3564 | 2 | } |
3565 | 1 | const char *body_str = NULL; |
3566 | 1 | size_t body_len = 0; |
3567 | 1 | int timeout_ms = 0; |
3568 | 1 | char **hdr_keys = NULL, **hdr_vals = NULL; |
3569 | 1 | size_t hdr_count = 0; |
3570 | 1 | if (argc == 3 && args[2].type == VAL_MAP) { |
3571 | 0 | LatValue *bv = (LatValue *)lat_map_get(args[2].as.map.map, "body"); |
3572 | 0 | if (bv && bv->type == VAL_STR) { body_str = bv->as.str_val; body_len = strlen(body_str); } |
3573 | 0 | LatValue *tv = (LatValue *)lat_map_get(args[2].as.map.map, "timeout"); |
3574 | 0 | if (tv && tv->type == VAL_INT) timeout_ms = (int)tv->as.int_val; |
3575 | 0 | LatValue *hm = (LatValue *)lat_map_get(args[2].as.map.map, "headers"); |
3576 | 0 | if (hm && hm->type == VAL_MAP) { |
3577 | 0 | hdr_count = lat_map_len(hm->as.map.map); |
3578 | 0 | hdr_keys = malloc(hdr_count * sizeof(char *)); |
3579 | 0 | hdr_vals = malloc(hdr_count * sizeof(char *)); |
3580 | 0 | size_t hi = 0; |
3581 | 0 | for (size_t i = 0; i < hm->as.map.map->cap && hi < hdr_count; i++) { |
3582 | 0 | if (hm->as.map.map->entries[i].state == MAP_OCCUPIED) { |
3583 | 0 | hdr_keys[hi] = (char *)hm->as.map.map->entries[i].key; |
3584 | 0 | LatValue *v = (LatValue *)hm->as.map.map->entries[i].value; |
3585 | 0 | hdr_vals[hi] = v->type == VAL_STR ? v->as.str_val : ""; |
3586 | 0 | hi++; |
3587 | 0 | } |
3588 | 0 | } |
3589 | 0 | } |
3590 | 0 | } |
3591 | 1 | HttpRequest hreq = { |
3592 | 1 | .method = args[0].as.str_val, .url = args[1].as.str_val, |
3593 | 1 | .header_keys = hdr_keys, .header_values = hdr_vals, .header_count = hdr_count, |
3594 | 1 | .body = body_str, .body_len = body_len, .timeout_ms = timeout_ms |
3595 | 1 | }; |
3596 | 1 | char *herr = NULL; |
3597 | 1 | HttpResponse *hresp = http_execute(&hreq, &herr); |
3598 | 1 | free(hdr_keys); free(hdr_vals); |
3599 | 3 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
3600 | 1 | if (!hresp) return eval_err(herr ? herr : strdup("http_request failed")); |
3601 | 0 | LatValue result = value_map_new(); |
3602 | 0 | LatValue st = value_int(hresp->status_code); |
3603 | 0 | lat_map_set(result.as.map.map, "status", &st); |
3604 | 0 | LatValue bd = value_string(hresp->body ? hresp->body : ""); |
3605 | 0 | lat_map_set(result.as.map.map, "body", &bd); |
3606 | 0 | LatValue hdrs = value_map_new(); |
3607 | 0 | for (size_t i = 0; i < hresp->header_count; i++) { |
3608 | 0 | LatValue hv = value_string(hresp->header_values[i]); |
3609 | 0 | lat_map_set(hdrs.as.map.map, hresp->header_keys[i], &hv); |
3610 | 0 | } |
3611 | 0 | lat_map_set(result.as.map.map, "headers", &hdrs); |
3612 | 0 | http_response_free(hresp); |
3613 | 0 | return eval_ok(result); |
3614 | 1 | } |
3615 | | |
3616 | | /* ── Math builtins ── */ |
3617 | | |
3618 | | /// @builtin abs(x: Int|Float) -> Int|Float |
3619 | | /// @category Math |
3620 | | /// Return the absolute value of a number. |
3621 | | /// @example abs(-5) // 5 |
3622 | 1.13k | if (strcmp(fn_name, "abs") == 0) { |
3623 | 3 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("abs() expects (Int|Float)")); } |
3624 | 3 | char *merr = NULL; |
3625 | 3 | LatValue result = math_abs(&args[0], &merr); |
3626 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3627 | 3 | free(args); |
3628 | 3 | if (merr) return eval_err(merr); |
3629 | 3 | return eval_ok(result); |
3630 | 3 | } |
3631 | | |
3632 | | /// @builtin floor(x: Int|Float) -> Int |
3633 | | /// @category Math |
3634 | | /// Round down to the nearest integer. |
3635 | | /// @example floor(3.7) // 3 |
3636 | 1.12k | if (strcmp(fn_name, "floor") == 0) { |
3637 | 1 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("floor() expects (Int|Float)")); } |
3638 | 1 | char *merr = NULL; |
3639 | 1 | LatValue result = math_floor(&args[0], &merr); |
3640 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3641 | 1 | free(args); |
3642 | 1 | if (merr) return eval_err(merr); |
3643 | 1 | return eval_ok(result); |
3644 | 1 | } |
3645 | | |
3646 | | /// @builtin ceil(x: Int|Float) -> Int |
3647 | | /// @category Math |
3648 | | /// Round up to the nearest integer. |
3649 | | /// @example ceil(3.2) // 4 |
3650 | 1.12k | if (strcmp(fn_name, "ceil") == 0) { |
3651 | 1 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("ceil() expects (Int|Float)")); } |
3652 | 1 | char *merr = NULL; |
3653 | 1 | LatValue result = math_ceil(&args[0], &merr); |
3654 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3655 | 1 | free(args); |
3656 | 1 | if (merr) return eval_err(merr); |
3657 | 1 | return eval_ok(result); |
3658 | 1 | } |
3659 | | |
3660 | | /// @builtin round(x: Int|Float) -> Int |
3661 | | /// @category Math |
3662 | | /// Round to the nearest integer. |
3663 | | /// @example round(3.5) // 4 |
3664 | 1.12k | if (strcmp(fn_name, "round") == 0) { |
3665 | 2 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("round() expects (Int|Float)")); } |
3666 | 2 | char *merr = NULL; |
3667 | 2 | LatValue result = math_round(&args[0], &merr); |
3668 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3669 | 2 | free(args); |
3670 | 2 | if (merr) return eval_err(merr); |
3671 | 2 | return eval_ok(result); |
3672 | 2 | } |
3673 | | |
3674 | | /// @builtin sqrt(x: Int|Float) -> Float |
3675 | | /// @category Math |
3676 | | /// Return the square root of a number. |
3677 | | /// @example sqrt(16) // 4.0 |
3678 | 1.12k | if (strcmp(fn_name, "sqrt") == 0) { |
3679 | 3 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("sqrt() expects (Int|Float)")); } |
3680 | 3 | char *merr = NULL; |
3681 | 3 | LatValue result = math_sqrt(&args[0], &merr); |
3682 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3683 | 3 | free(args); |
3684 | 3 | if (merr) return eval_err(merr); |
3685 | 2 | return eval_ok(result); |
3686 | 3 | } |
3687 | | |
3688 | | /// @builtin pow(base: Int|Float, exp: Int|Float) -> Float |
3689 | | /// @category Math |
3690 | | /// Raise base to the power of exp. |
3691 | | /// @example pow(2, 10) // 1024.0 |
3692 | 1.12k | if (strcmp(fn_name, "pow") == 0) { |
3693 | 2 | if (argc != 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("pow() expects (Int|Float, Int|Float)")); } |
3694 | 2 | char *merr = NULL; |
3695 | 2 | LatValue result = math_pow(&args[0], &args[1], &merr); |
3696 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3697 | 2 | free(args); |
3698 | 2 | if (merr) return eval_err(merr); |
3699 | 2 | return eval_ok(result); |
3700 | 2 | } |
3701 | | |
3702 | | /// @builtin min(a: Int|Float, b: Int|Float) -> Int|Float |
3703 | | /// @category Math |
3704 | | /// Return the smaller of two numbers. |
3705 | | /// @example min(3, 7) // 3 |
3706 | 1.11k | if (strcmp(fn_name, "min") == 0) { |
3707 | 2 | if (argc != 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("min() expects (Int|Float, Int|Float)")); } |
3708 | 2 | char *merr = NULL; |
3709 | 2 | LatValue result = math_min(&args[0], &args[1], &merr); |
3710 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3711 | 2 | free(args); |
3712 | 2 | if (merr) return eval_err(merr); |
3713 | 2 | return eval_ok(result); |
3714 | 2 | } |
3715 | | |
3716 | | /// @builtin max(a: Int|Float, b: Int|Float) -> Int|Float |
3717 | | /// @category Math |
3718 | | /// Return the larger of two numbers. |
3719 | | /// @example max(3, 7) // 7 |
3720 | 1.11k | if (strcmp(fn_name, "max") == 0) { |
3721 | 2 | if (argc != 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("max() expects (Int|Float, Int|Float)")); } |
3722 | 2 | char *merr = NULL; |
3723 | 2 | LatValue result = math_max(&args[0], &args[1], &merr); |
3724 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3725 | 2 | free(args); |
3726 | 2 | if (merr) return eval_err(merr); |
3727 | 2 | return eval_ok(result); |
3728 | 2 | } |
3729 | | |
3730 | | /// @builtin random() -> Float |
3731 | | /// @category Math |
3732 | | /// Return a random float between 0.0 (inclusive) and 1.0 (exclusive). |
3733 | | /// @example random() // 0.7231... |
3734 | 1.11k | if (strcmp(fn_name, "random") == 0) { |
3735 | 1 | if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("random() expects no arguments")); } |
3736 | 1 | free(args); |
3737 | 1 | return eval_ok(math_random()); |
3738 | 1 | } |
3739 | | |
3740 | | /// @builtin random_int(min: Int, max: Int) -> Int |
3741 | | /// @category Math |
3742 | | /// Return a random integer in the range [min, max). |
3743 | | /// @example random_int(1, 100) // 42 |
3744 | 1.11k | if (strcmp(fn_name, "random_int") == 0) { |
3745 | 1 | if (argc != 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("random_int() expects (Int, Int)")); } |
3746 | 1 | char *merr = NULL; |
3747 | 1 | LatValue result = math_random_int(&args[0], &args[1], &merr); |
3748 | 3 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3749 | 1 | free(args); |
3750 | 1 | if (merr) return eval_err(merr); |
3751 | 1 | return eval_ok(result); |
3752 | 1 | } |
3753 | | |
3754 | | /// @builtin log(x: Int|Float) -> Float |
3755 | | /// @category Math |
3756 | | /// Return the natural logarithm (base e) of a number. |
3757 | | /// @example log(math_e()) // 1.0 |
3758 | 1.11k | if (strcmp(fn_name, "log") == 0) { |
3759 | 1 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("log() expects (Int|Float)")); } |
3760 | 1 | char *merr = NULL; |
3761 | 1 | LatValue result = math_log(&args[0], &merr); |
3762 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3763 | 1 | free(args); |
3764 | 1 | if (merr) return eval_err(merr); |
3765 | 1 | return eval_ok(result); |
3766 | 1 | } |
3767 | | |
3768 | | /// @builtin log2(x: Int|Float) -> Float |
3769 | | /// @category Math |
3770 | | /// Return the base-2 logarithm of a number. |
3771 | | /// @example log2(8) // 3.0 |
3772 | 1.11k | if (strcmp(fn_name, "log2") == 0) { |
3773 | 1 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("log2() expects (Int|Float)")); } |
3774 | 1 | char *merr = NULL; |
3775 | 1 | LatValue result = math_log2(&args[0], &merr); |
3776 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3777 | 1 | free(args); |
3778 | 1 | if (merr) return eval_err(merr); |
3779 | 1 | return eval_ok(result); |
3780 | 1 | } |
3781 | | |
3782 | | /// @builtin log10(x: Int|Float) -> Float |
3783 | | /// @category Math |
3784 | | /// Return the base-10 logarithm of a number. |
3785 | | /// @example log10(1000) // 3.0 |
3786 | 1.11k | if (strcmp(fn_name, "log10") == 0) { |
3787 | 1 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("log10() expects (Int|Float)")); } |
3788 | 1 | char *merr = NULL; |
3789 | 1 | LatValue result = math_log10(&args[0], &merr); |
3790 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3791 | 1 | free(args); |
3792 | 1 | if (merr) return eval_err(merr); |
3793 | 1 | return eval_ok(result); |
3794 | 1 | } |
3795 | | |
3796 | | /// @builtin sin(x: Int|Float) -> Float |
3797 | | /// @category Math |
3798 | | /// Return the sine of an angle in radians. |
3799 | | /// @example sin(0) // 0.0 |
3800 | 1.10k | if (strcmp(fn_name, "sin") == 0) { |
3801 | 3 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("sin() expects (Int|Float)")); } |
3802 | 3 | char *merr = NULL; |
3803 | 3 | LatValue result = math_sin(&args[0], &merr); |
3804 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3805 | 3 | free(args); |
3806 | 3 | if (merr) return eval_err(merr); |
3807 | 3 | return eval_ok(result); |
3808 | 3 | } |
3809 | | |
3810 | | /// @builtin cos(x: Int|Float) -> Float |
3811 | | /// @category Math |
3812 | | /// Return the cosine of an angle in radians. |
3813 | | /// @example cos(0) // 1.0 |
3814 | 1.10k | if (strcmp(fn_name, "cos") == 0) { |
3815 | 1 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("cos() expects (Int|Float)")); } |
3816 | 1 | char *merr = NULL; |
3817 | 1 | LatValue result = math_cos(&args[0], &merr); |
3818 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3819 | 1 | free(args); |
3820 | 1 | if (merr) return eval_err(merr); |
3821 | 1 | return eval_ok(result); |
3822 | 1 | } |
3823 | | |
3824 | | /// @builtin tan(x: Int|Float) -> Float |
3825 | | /// @category Math |
3826 | | /// Return the tangent of an angle in radians. |
3827 | | /// @example tan(0) // 0.0 |
3828 | 1.10k | if (strcmp(fn_name, "tan") == 0) { |
3829 | 1 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tan() expects (Int|Float)")); } |
3830 | 1 | char *merr = NULL; |
3831 | 1 | LatValue result = math_tan(&args[0], &merr); |
3832 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3833 | 1 | free(args); |
3834 | 1 | if (merr) return eval_err(merr); |
3835 | 1 | return eval_ok(result); |
3836 | 1 | } |
3837 | | |
3838 | | /// @builtin atan2(y: Int|Float, x: Int|Float) -> Float |
3839 | | /// @category Math |
3840 | | /// Return the two-argument arctangent in radians. |
3841 | | /// @example atan2(1, 1) // 0.7853... |
3842 | 1.10k | if (strcmp(fn_name, "atan2") == 0) { |
3843 | 1 | if (argc != 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("atan2() expects (Int|Float, Int|Float)")); } |
3844 | 1 | char *merr = NULL; |
3845 | 1 | LatValue result = math_atan2(&args[0], &args[1], &merr); |
3846 | 3 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3847 | 1 | free(args); |
3848 | 1 | if (merr) return eval_err(merr); |
3849 | 1 | return eval_ok(result); |
3850 | 1 | } |
3851 | | |
3852 | | /// @builtin clamp(x: Int|Float, lo: Int|Float, hi: Int|Float) -> Int|Float |
3853 | | /// @category Math |
3854 | | /// Clamp a value between a minimum and maximum. |
3855 | | /// @example clamp(15, 0, 10) // 10 |
3856 | 1.10k | if (strcmp(fn_name, "clamp") == 0) { |
3857 | 3 | if (argc != 3) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("clamp() expects (Int|Float, Int|Float, Int|Float)")); } |
3858 | 3 | char *merr = NULL; |
3859 | 3 | LatValue result = math_clamp(&args[0], &args[1], &args[2], &merr); |
3860 | 12 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3861 | 3 | free(args); |
3862 | 3 | if (merr) return eval_err(merr); |
3863 | 3 | return eval_ok(result); |
3864 | 3 | } |
3865 | | |
3866 | | /// @builtin math_pi() -> Float |
3867 | | /// @category Math |
3868 | | /// Return the mathematical constant pi. |
3869 | | /// @example math_pi() // 3.14159265358979... |
3870 | 1.10k | if (strcmp(fn_name, "math_pi") == 0) { |
3871 | 1 | if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("math_pi() expects no arguments")); } |
3872 | 1 | free(args); |
3873 | 1 | return eval_ok(math_pi()); |
3874 | 1 | } |
3875 | | |
3876 | | /// @builtin math_e() -> Float |
3877 | | /// @category Math |
3878 | | /// Return Euler's number (e). |
3879 | | /// @example math_e() // 2.71828182845904... |
3880 | 1.09k | if (strcmp(fn_name, "math_e") == 0) { |
3881 | 2 | if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("math_e() expects no arguments")); } |
3882 | 2 | free(args); |
3883 | 2 | return eval_ok(math_e()); |
3884 | 2 | } |
3885 | | |
3886 | | /// @builtin asin(x: Int|Float) -> Float |
3887 | | /// @category Math |
3888 | | /// Return the arcsine in radians. |
3889 | | /// @example asin(1) // 1.5707... |
3890 | 1.09k | if (strcmp(fn_name, "asin") == 0) { |
3891 | 1 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("asin() expects (Int|Float)")); } |
3892 | 1 | char *merr = NULL; |
3893 | 1 | LatValue result = math_asin(&args[0], &merr); |
3894 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3895 | 1 | free(args); |
3896 | 1 | if (merr) return eval_err(merr); |
3897 | 1 | return eval_ok(result); |
3898 | 1 | } |
3899 | | |
3900 | | /// @builtin acos(x: Int|Float) -> Float |
3901 | | /// @category Math |
3902 | | /// Return the arccosine in radians. |
3903 | | /// @example acos(1) // 0.0 |
3904 | 1.09k | if (strcmp(fn_name, "acos") == 0) { |
3905 | 1 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("acos() expects (Int|Float)")); } |
3906 | 1 | char *merr = NULL; |
3907 | 1 | LatValue result = math_acos(&args[0], &merr); |
3908 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3909 | 1 | free(args); |
3910 | 1 | if (merr) return eval_err(merr); |
3911 | 1 | return eval_ok(result); |
3912 | 1 | } |
3913 | | |
3914 | | /// @builtin atan(x: Int|Float) -> Float |
3915 | | /// @category Math |
3916 | | /// Return the arctangent in radians. |
3917 | | /// @example atan(1) // 0.7853... |
3918 | 1.09k | if (strcmp(fn_name, "atan") == 0) { |
3919 | 1 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("atan() expects (Int|Float)")); } |
3920 | 1 | char *merr = NULL; |
3921 | 1 | LatValue result = math_atan(&args[0], &merr); |
3922 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3923 | 1 | free(args); |
3924 | 1 | if (merr) return eval_err(merr); |
3925 | 1 | return eval_ok(result); |
3926 | 1 | } |
3927 | | |
3928 | | /// @builtin exp(x: Int|Float) -> Float |
3929 | | /// @category Math |
3930 | | /// Return e raised to the power of x. |
3931 | | /// @example exp(1) // 2.71828... |
3932 | 1.09k | if (strcmp(fn_name, "exp") == 0) { |
3933 | 1 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("exp() expects (Int|Float)")); } |
3934 | 1 | char *merr = NULL; |
3935 | 1 | LatValue result = math_exp(&args[0], &merr); |
3936 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3937 | 1 | free(args); |
3938 | 1 | if (merr) return eval_err(merr); |
3939 | 1 | return eval_ok(result); |
3940 | 1 | } |
3941 | | |
3942 | | /// @builtin sign(x: Int|Float) -> Int |
3943 | | /// @category Math |
3944 | | /// Return -1, 0, or 1 indicating the sign of a number. |
3945 | | /// @example sign(-42) // -1 |
3946 | 1.09k | if (strcmp(fn_name, "sign") == 0) { |
3947 | 3 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("sign() expects (Int|Float)")); } |
3948 | 3 | char *merr = NULL; |
3949 | 3 | LatValue result = math_sign(&args[0], &merr); |
3950 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3951 | 3 | free(args); |
3952 | 3 | if (merr) return eval_err(merr); |
3953 | 3 | return eval_ok(result); |
3954 | 3 | } |
3955 | | |
3956 | | /// @builtin gcd(a: Int, b: Int) -> Int |
3957 | | /// @category Math |
3958 | | /// Return the greatest common divisor of two integers. |
3959 | | /// @example gcd(12, 8) // 4 |
3960 | 1.09k | if (strcmp(fn_name, "gcd") == 0) { |
3961 | 1 | if (argc != 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("gcd() expects (Int, Int)")); } |
3962 | 1 | char *merr = NULL; |
3963 | 1 | LatValue result = math_gcd(&args[0], &args[1], &merr); |
3964 | 3 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3965 | 1 | free(args); |
3966 | 1 | if (merr) return eval_err(merr); |
3967 | 1 | return eval_ok(result); |
3968 | 1 | } |
3969 | | |
3970 | | /// @builtin lcm(a: Int, b: Int) -> Int |
3971 | | /// @category Math |
3972 | | /// Return the least common multiple of two integers. |
3973 | | /// @example lcm(4, 6) // 12 |
3974 | 1.08k | if (strcmp(fn_name, "lcm") == 0) { |
3975 | 1 | if (argc != 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("lcm() expects (Int, Int)")); } |
3976 | 1 | char *merr = NULL; |
3977 | 1 | LatValue result = math_lcm(&args[0], &args[1], &merr); |
3978 | 3 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3979 | 1 | free(args); |
3980 | 1 | if (merr) return eval_err(merr); |
3981 | 1 | return eval_ok(result); |
3982 | 1 | } |
3983 | | |
3984 | | /// @builtin float_to_bits(x: Float) -> Int |
3985 | | /// @category Math |
3986 | | /// Reinterpret a float as its IEEE 754 bit pattern (64-bit integer). |
3987 | | /// @example float_to_bits(1.0) // 4607182418800017408 |
3988 | 1.08k | if (strcmp(fn_name, "float_to_bits") == 0) { |
3989 | 0 | if (argc != 1 || args[0].type != VAL_FLOAT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("float_to_bits() expects 1 Float argument")); } |
3990 | 0 | double d = args[0].as.float_val; |
3991 | 0 | uint64_t bits; memcpy(&bits, &d, 8); |
3992 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
3993 | 0 | free(args); |
3994 | 0 | return eval_ok(value_int((int64_t)bits)); |
3995 | 0 | } |
3996 | | |
3997 | | /// @builtin bits_to_float(x: Int) -> Float |
3998 | | /// @category Math |
3999 | | /// Reinterpret a 64-bit integer as an IEEE 754 float. |
4000 | | /// @example bits_to_float(4607182418800017408) // 1.0 |
4001 | 1.08k | if (strcmp(fn_name, "bits_to_float") == 0) { |
4002 | 0 | if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("bits_to_float() expects 1 Int argument")); } |
4003 | 0 | uint64_t bits = (uint64_t)args[0].as.int_val; |
4004 | 0 | double d; memcpy(&d, &bits, 8); |
4005 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4006 | 0 | free(args); |
4007 | 0 | return eval_ok(value_float(d)); |
4008 | 0 | } |
4009 | | |
4010 | | /// @builtin is_nan(x: Int|Float) -> Bool |
4011 | | /// @category Math |
4012 | | /// Check if a value is NaN (not a number). |
4013 | | /// @example is_nan(0.0 / 0.0) // true |
4014 | 1.08k | if (strcmp(fn_name, "is_nan") == 0) { |
4015 | 2 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("is_nan() expects (Int|Float)")); } |
4016 | 2 | char *merr = NULL; |
4017 | 2 | LatValue result = math_is_nan(&args[0], &merr); |
4018 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4019 | 2 | free(args); |
4020 | 2 | if (merr) return eval_err(merr); |
4021 | 2 | return eval_ok(result); |
4022 | 2 | } |
4023 | | |
4024 | | /// @builtin is_inf(x: Int|Float) -> Bool |
4025 | | /// @category Math |
4026 | | /// Check if a value is positive or negative infinity. |
4027 | | /// @example is_inf(1.0 / 0.0) // true |
4028 | 1.08k | if (strcmp(fn_name, "is_inf") == 0) { |
4029 | 2 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("is_inf() expects (Int|Float)")); } |
4030 | 2 | char *merr = NULL; |
4031 | 2 | LatValue result = math_is_inf(&args[0], &merr); |
4032 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4033 | 2 | free(args); |
4034 | 2 | if (merr) return eval_err(merr); |
4035 | 2 | return eval_ok(result); |
4036 | 2 | } |
4037 | | |
4038 | | /// @builtin sinh(x: Int|Float) -> Float |
4039 | | /// @category Math |
4040 | | /// Return the hyperbolic sine. |
4041 | | /// @example sinh(1) // 1.1752... |
4042 | 1.08k | if (strcmp(fn_name, "sinh") == 0) { |
4043 | 1 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("sinh() expects (Int|Float)")); } |
4044 | 1 | char *merr = NULL; |
4045 | 1 | LatValue result = math_sinh(&args[0], &merr); |
4046 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4047 | 1 | free(args); |
4048 | 1 | if (merr) return eval_err(merr); |
4049 | 1 | return eval_ok(result); |
4050 | 1 | } |
4051 | | |
4052 | | /// @builtin cosh(x: Int|Float) -> Float |
4053 | | /// @category Math |
4054 | | /// Return the hyperbolic cosine. |
4055 | | /// @example cosh(0) // 1.0 |
4056 | 1.08k | if (strcmp(fn_name, "cosh") == 0) { |
4057 | 1 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("cosh() expects (Int|Float)")); } |
4058 | 1 | char *merr = NULL; |
4059 | 1 | LatValue result = math_cosh(&args[0], &merr); |
4060 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4061 | 1 | free(args); |
4062 | 1 | if (merr) return eval_err(merr); |
4063 | 1 | return eval_ok(result); |
4064 | 1 | } |
4065 | | |
4066 | | /// @builtin tanh(x: Int|Float) -> Float |
4067 | | /// @category Math |
4068 | | /// Return the hyperbolic tangent. |
4069 | | /// @example tanh(0) // 0.0 |
4070 | 1.08k | if (strcmp(fn_name, "tanh") == 0) { |
4071 | 1 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tanh() expects (Int|Float)")); } |
4072 | 1 | char *merr = NULL; |
4073 | 1 | LatValue result = math_tanh(&args[0], &merr); |
4074 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4075 | 1 | free(args); |
4076 | 1 | if (merr) return eval_err(merr); |
4077 | 1 | return eval_ok(result); |
4078 | 1 | } |
4079 | | |
4080 | | /// @builtin lerp(a: Int|Float, b: Int|Float, t: Int|Float) -> Float |
4081 | | /// @category Math |
4082 | | /// Linear interpolation between a and b by factor t. |
4083 | | /// @example lerp(0, 10, 0.5) // 5.0 |
4084 | 1.08k | if (strcmp(fn_name, "lerp") == 0) { |
4085 | 2 | if (argc != 3) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("lerp() expects (Int|Float, Int|Float, Int|Float)")); } |
4086 | 2 | char *merr = NULL; |
4087 | 2 | LatValue result = math_lerp(&args[0], &args[1], &args[2], &merr); |
4088 | 8 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4089 | 2 | free(args); |
4090 | 2 | if (merr) return eval_err(merr); |
4091 | 2 | return eval_ok(result); |
4092 | 2 | } |
4093 | | |
4094 | | /* ── range() builtin ── */ |
4095 | | |
4096 | | /// @builtin range(start: Int, end: Int, step?: Int) -> Array |
4097 | | /// @category Type Constructors |
4098 | | /// Generate an array of integers from start (inclusive) to end (exclusive). |
4099 | | /// @example range(0, 5) // [0, 1, 2, 3, 4] |
4100 | | /// @example range(0, 10, 2) // [0, 2, 4, 6, 8] |
4101 | 1.07k | if (strcmp(fn_name, "range") == 0) { |
4102 | 7 | if (argc < 2 || argc > 3) { |
4103 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4104 | 0 | free(args); |
4105 | 0 | return eval_err(strdup("range() expects 2 or 3 integer arguments (start, end, step?)")); |
4106 | 0 | } |
4107 | 7 | if (args[0].type != VAL_INT || args[1].type != VAL_INT) { |
4108 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4109 | 0 | free(args); |
4110 | 0 | return eval_err(strdup("range() start and end must be integers")); |
4111 | 0 | } |
4112 | 7 | int64_t rstart = args[0].as.int_val; |
4113 | 7 | int64_t rend = args[1].as.int_val; |
4114 | 7 | int64_t rstep = (rstart <= rend) ? 1 : -1; |
4115 | 7 | if (argc == 3) { |
4116 | 4 | if (args[2].type != VAL_INT) { |
4117 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4118 | 0 | free(args); |
4119 | 0 | return eval_err(strdup("range() step must be an integer")); |
4120 | 0 | } |
4121 | 4 | rstep = args[2].as.int_val; |
4122 | 4 | } |
4123 | 25 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4124 | 7 | free(args); |
4125 | 7 | if (rstep == 0) { |
4126 | 1 | return eval_err(strdup("range() step cannot be 0")); |
4127 | 1 | } |
4128 | | /* Calculate count and build array */ |
4129 | 6 | size_t rcount = 0; |
4130 | 6 | if (rstep > 0 && rstart < rend) { |
4131 | 2 | rcount = (size_t)((rend - rstart + rstep - 1) / rstep); |
4132 | 4 | } else if (rstep < 0 && rstart > rend) { |
4133 | 2 | rcount = (size_t)((rstart - rend + (-rstep) - 1) / (-rstep)); |
4134 | 2 | } |
4135 | 6 | LatValue *relems = malloc((rcount > 0 ? rcount : 1) * sizeof(LatValue)); |
4136 | 6 | int64_t rcur = rstart; |
4137 | 25 | for (size_t ri = 0; ri < rcount; ri++) { |
4138 | 19 | relems[ri] = value_int(rcur); |
4139 | 19 | rcur += rstep; |
4140 | 19 | } |
4141 | 6 | LatValue range_arr = value_array(relems, rcount); |
4142 | 6 | free(relems); |
4143 | 6 | return eval_ok(range_arr); |
4144 | 7 | } |
4145 | | |
4146 | | /* ── Type coercion builtins ── */ |
4147 | | |
4148 | | /// @builtin to_int(val: Any) -> Int |
4149 | | /// @category Type Conversion |
4150 | | /// Convert a value to an integer (truncates floats, parses strings). |
4151 | | /// @example to_int(3.9) // 3 |
4152 | 1.07k | if (strcmp(fn_name, "to_int") == 0) { |
4153 | 0 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("to_int() expects (value)")); } |
4154 | 0 | char *terr = NULL; |
4155 | 0 | LatValue result = type_to_int(&args[0], &terr); |
4156 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4157 | 0 | free(args); |
4158 | 0 | if (terr) return eval_err(terr); |
4159 | 0 | return eval_ok(result); |
4160 | 0 | } |
4161 | | |
4162 | | /// @builtin to_float(val: Any) -> Float |
4163 | | /// @category Type Conversion |
4164 | | /// Convert a value to a floating-point number. |
4165 | | /// @example to_float(42) // 42.0 |
4166 | 1.07k | if (strcmp(fn_name, "to_float") == 0) { |
4167 | 0 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("to_float() expects (value)")); } |
4168 | 0 | char *terr = NULL; |
4169 | 0 | LatValue result = type_to_float(&args[0], &terr); |
4170 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4171 | 0 | free(args); |
4172 | 0 | if (terr) return eval_err(terr); |
4173 | 0 | return eval_ok(result); |
4174 | 0 | } |
4175 | | |
4176 | | /* ── Environment variable builtins ── */ |
4177 | | |
4178 | | /// @builtin env(name: String) -> String|Unit |
4179 | | /// @category Environment |
4180 | | /// Get an environment variable's value, or unit if not set. |
4181 | | /// @example env("HOME") // "/home/user" |
4182 | 1.07k | if (strcmp(fn_name, "env") == 0) { |
4183 | 4 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("env() expects (String)")); } |
4184 | 3 | char *val = envvar_get(args[0].as.str_val); |
4185 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4186 | 3 | free(args); |
4187 | 3 | if (!val) return eval_ok(value_unit()); |
4188 | 2 | return eval_ok(value_string_owned(val)); |
4189 | 3 | } |
4190 | | |
4191 | | /// @builtin env_set(name: String, value: String) -> Unit |
4192 | | /// @category Environment |
4193 | | /// Set an environment variable. |
4194 | | /// @example env_set("MY_VAR", "hello") |
4195 | 1.06k | if (strcmp(fn_name, "env_set") == 0) { |
4196 | 3 | if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("env_set() expects (String, String)")); } |
4197 | 2 | char *eerr = NULL; |
4198 | 2 | bool ok = envvar_set(args[0].as.str_val, args[1].as.str_val, &eerr); |
4199 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4200 | 2 | free(args); |
4201 | 2 | if (!ok) return eval_err(eerr); |
4202 | 2 | return eval_ok(value_unit()); |
4203 | 2 | } |
4204 | | |
4205 | | /// @builtin env_keys() -> Array |
4206 | | /// @category Environment |
4207 | | /// Return an array of all environment variable names. |
4208 | | /// @example env_keys() // ["HOME", "PATH", ...] |
4209 | 1.06k | if (strcmp(fn_name, "env_keys") == 0) { |
4210 | 1 | if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("env_keys() expects no arguments")); } |
4211 | 1 | free(args); |
4212 | 1 | char **keys = NULL; |
4213 | 1 | size_t key_count = 0; |
4214 | 1 | envvar_keys(&keys, &key_count); |
4215 | 1 | LatValue *elems = NULL; |
4216 | 1 | if (key_count > 0) { |
4217 | 1 | elems = malloc(key_count * sizeof(LatValue)); |
4218 | 76 | for (size_t i = 0; i < key_count; i++) { |
4219 | 75 | elems[i] = value_string_owned(keys[i]); |
4220 | 75 | } |
4221 | 1 | } |
4222 | 1 | free(keys); |
4223 | 1 | LatValue arr = value_array(elems, key_count); |
4224 | 1 | free(elems); |
4225 | 1 | return eval_ok(arr); |
4226 | 1 | } |
4227 | | |
4228 | | /* ── Time builtins ── */ |
4229 | | |
4230 | | /// @builtin time() -> Int |
4231 | | /// @category Date & Time |
4232 | | /// Return the current Unix timestamp in milliseconds. |
4233 | | /// @example time() // 1700000000000 |
4234 | 1.06k | if (strcmp(fn_name, "time") == 0) { |
4235 | 17 | if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("time() expects no arguments")); } |
4236 | 16 | free(args); |
4237 | 16 | return eval_ok(value_int(time_now_ms())); |
4238 | 17 | } |
4239 | | |
4240 | | /// @builtin sleep(ms: Int) -> Unit |
4241 | | /// @category Date & Time |
4242 | | /// Pause execution for the given number of milliseconds. |
4243 | | /// @example sleep(1000) // sleeps for 1 second |
4244 | 1.04k | if (strcmp(fn_name, "sleep") == 0) { |
4245 | 2 | if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("sleep() expects (Int milliseconds)")); } |
4246 | 1 | char *terr = NULL; |
4247 | 1 | bool ok = time_sleep_ms(args[0].as.int_val, &terr); |
4248 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4249 | 1 | free(args); |
4250 | 1 | if (!ok) return eval_err(terr); |
4251 | 1 | return eval_ok(value_unit()); |
4252 | 1 | } |
4253 | | |
4254 | | /* ── Process/system builtins ── */ |
4255 | | |
4256 | | /// @builtin cwd() -> String |
4257 | | /// @category Process |
4258 | | /// Return the current working directory. |
4259 | | /// @example cwd() // "/home/user/project" |
4260 | 1.04k | if (strcmp(fn_name, "cwd") == 0) { |
4261 | 1 | if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("cwd() expects no arguments")); } |
4262 | 1 | free(args); |
4263 | 1 | char *cwd_err = NULL; |
4264 | 1 | char *dir = process_cwd(&cwd_err); |
4265 | 1 | if (!dir) return eval_err(cwd_err); |
4266 | 1 | return eval_ok(value_string_owned(dir)); |
4267 | 1 | } |
4268 | | |
4269 | | /// @builtin exec(cmd: String) -> Map |
4270 | | /// @category Process |
4271 | | /// Execute a command directly (no shell), returning {stdout, stderr, status}. |
4272 | | /// @example exec("ls -la") // {stdout: "...", stderr: "", status: 0} |
4273 | 1.04k | if (strcmp(fn_name, "exec") == 0) { |
4274 | 1 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("exec() expects 1 argument")); } |
4275 | 1 | if (args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("exec() expects a string command")); } |
4276 | 1 | char *exec_err = NULL; |
4277 | 1 | LatValue result = process_exec(args[0].as.str_val, &exec_err); |
4278 | 2 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4279 | 1 | free(args); |
4280 | 1 | if (exec_err) return eval_err(exec_err); |
4281 | 1 | return eval_ok(result); |
4282 | 1 | } |
4283 | | |
4284 | | /// @builtin shell(cmd: String) -> Map |
4285 | | /// @category Process |
4286 | | /// Execute a command via the system shell, returning {stdout, stderr, status}. |
4287 | | /// @example shell("echo hello") // {stdout: "hello\n", stderr: "", status: 0} |
4288 | 1.04k | if (strcmp(fn_name, "shell") == 0) { |
4289 | 3 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("shell() expects 1 argument")); } |
4290 | 3 | if (args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("shell() expects a string command")); } |
4291 | 3 | char *shell_err = NULL; |
4292 | 3 | LatValue result = process_shell(args[0].as.str_val, &shell_err); |
4293 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4294 | 3 | free(args); |
4295 | 3 | if (shell_err) return eval_err(shell_err); |
4296 | 3 | return eval_ok(result); |
4297 | 3 | } |
4298 | | |
4299 | | /// @builtin args() -> Array |
4300 | | /// @category Process |
4301 | | /// Return command-line arguments as an array of strings. |
4302 | | /// @example args() // ["script.lat", "--flag"] |
4303 | 1.04k | if (strcmp(fn_name, "args") == 0) { |
4304 | 1 | if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("args() expects no arguments")); } |
4305 | 1 | free(args); |
4306 | | #ifdef __EMSCRIPTEN__ |
4307 | | LatValue arr = value_array(NULL, 0); |
4308 | | return eval_ok(arr); |
4309 | | #else |
4310 | 1 | int ac = ev->prog_argc; |
4311 | 1 | char **av = ev->prog_argv; |
4312 | 1 | LatValue *elems = NULL; |
4313 | 1 | if (ac > 0) { |
4314 | 0 | elems = malloc((size_t)ac * sizeof(LatValue)); |
4315 | 0 | for (int i = 0; i < ac; i++) |
4316 | 0 | elems[i] = value_string(av[i]); |
4317 | 0 | } |
4318 | 1 | LatValue arr = value_array(elems, (size_t)ac); |
4319 | 1 | free(elems); |
4320 | 1 | return eval_ok(arr); |
4321 | 1 | #endif |
4322 | 1 | } |
4323 | | |
4324 | | /// @builtin platform() -> String |
4325 | | /// @category Process |
4326 | | /// Return the operating system name ("darwin", "linux", etc.). |
4327 | | /// @example platform() // "darwin" |
4328 | 1.03k | if (strcmp(fn_name, "platform") == 0) { |
4329 | 2 | if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("platform() expects no arguments")); } |
4330 | 2 | free(args); |
4331 | 2 | return eval_ok(value_string(process_platform())); |
4332 | 2 | } |
4333 | | |
4334 | | /// @builtin hostname() -> String |
4335 | | /// @category Process |
4336 | | /// Return the system hostname. |
4337 | | /// @example hostname() // "my-machine" |
4338 | 1.03k | if (strcmp(fn_name, "hostname") == 0) { |
4339 | 1 | if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("hostname() expects no arguments")); } |
4340 | 1 | free(args); |
4341 | 1 | char *h_err = NULL; |
4342 | 1 | char *name = process_hostname(&h_err); |
4343 | 1 | if (!name) return eval_err(h_err); |
4344 | 1 | LatValue v = value_string_owned(name); |
4345 | 1 | return eval_ok(v); |
4346 | 1 | } |
4347 | | |
4348 | | /// @builtin pid() -> Int |
4349 | | /// @category Process |
4350 | | /// Return the current process ID. |
4351 | | /// @example pid() // 12345 |
4352 | 1.03k | if (strcmp(fn_name, "pid") == 0) { |
4353 | 1 | if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("pid() expects no arguments")); } |
4354 | 1 | free(args); |
4355 | 1 | return eval_ok(value_int((int64_t)process_pid())); |
4356 | 1 | } |
4357 | | |
4358 | | /* ── URL encoding builtins ── */ |
4359 | | |
4360 | | /// @builtin url_encode(s: String) -> String |
4361 | | /// @category URL |
4362 | | /// Percent-encode a string for use in URLs. |
4363 | | /// @example url_encode("hello world") // "hello%20world" |
4364 | 1.03k | if (strcmp(fn_name, "url_encode") == 0) { |
4365 | 2 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("url_encode() expects (String)")); } |
4366 | 2 | const char *src = args[0].as.str_val; |
4367 | 2 | size_t slen = strlen(src); |
4368 | | /* Worst case: every byte becomes %XX (3x expansion) */ |
4369 | 2 | size_t cap = slen * 3 + 1; |
4370 | 2 | char *out = malloc(cap); |
4371 | 2 | size_t j = 0; |
4372 | 26 | for (size_t i = 0; i < slen; i++) { |
4373 | 24 | unsigned char c = (unsigned char)src[i]; |
4374 | 24 | if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || |
4375 | 24 | (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~') { |
4376 | 20 | out[j++] = (char)c; |
4377 | 20 | } else { |
4378 | 4 | snprintf(out + j, 4, "%%%02X", c); |
4379 | 4 | j += 3; |
4380 | 4 | } |
4381 | 24 | } |
4382 | 2 | out[j] = '\0'; |
4383 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4384 | 2 | free(args); |
4385 | 2 | return eval_ok(value_string_owned(out)); |
4386 | 2 | } |
4387 | | |
4388 | | /// @builtin url_decode(s: String) -> String |
4389 | | /// @category URL |
4390 | | /// Decode a percent-encoded URL string. |
4391 | | /// @example url_decode("hello%20world") // "hello world" |
4392 | 1.03k | if (strcmp(fn_name, "url_decode") == 0) { |
4393 | 2 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("url_decode() expects (String)")); } |
4394 | 2 | const char *src = args[0].as.str_val; |
4395 | 2 | size_t slen = strlen(src); |
4396 | 2 | char *out = malloc(slen + 1); |
4397 | 2 | size_t j = 0; |
4398 | 20 | for (size_t i = 0; i < slen; i++) { |
4399 | 18 | if (src[i] == '%' && i + 2 < slen) { |
4400 | 1 | char hex[3] = { src[i+1], src[i+2], '\0' }; |
4401 | 1 | char *end = NULL; |
4402 | 1 | unsigned long val = strtoul(hex, &end, 16); |
4403 | 1 | if (end == hex + 2) { |
4404 | 1 | out[j++] = (char)val; |
4405 | 1 | i += 2; |
4406 | 1 | } else { |
4407 | 0 | out[j++] = src[i]; |
4408 | 0 | } |
4409 | 17 | } else if (src[i] == '+') { |
4410 | 1 | out[j++] = ' '; |
4411 | 16 | } else { |
4412 | 16 | out[j++] = src[i]; |
4413 | 16 | } |
4414 | 18 | } |
4415 | 2 | out[j] = '\0'; |
4416 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4417 | 2 | free(args); |
4418 | 2 | return eval_ok(value_string_owned(out)); |
4419 | 2 | } |
4420 | | |
4421 | | /* ── CSV builtins ── */ |
4422 | | |
4423 | | /// @builtin csv_parse(s: String) -> Array |
4424 | | /// @category CSV |
4425 | | /// Parse a CSV string into an array of arrays (rows of fields). |
4426 | | /// @example csv_parse("a,b\n1,2") // [["a", "b"], ["1", "2"]] |
4427 | 1.03k | if (strcmp(fn_name, "csv_parse") == 0) { |
4428 | 3 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("csv_parse() expects (String)")); } |
4429 | 3 | const char *input = args[0].as.str_val; |
4430 | 3 | size_t pos = 0; |
4431 | 3 | size_t input_len = strlen(input); |
4432 | | |
4433 | | /* Collect rows into a temporary array */ |
4434 | 3 | size_t rows_cap = 8; |
4435 | 3 | size_t rows_len = 0; |
4436 | 3 | LatValue *rows = malloc(rows_cap * sizeof(LatValue)); |
4437 | | |
4438 | 9 | while (pos < input_len) { |
4439 | | /* Parse one row: collect fields */ |
4440 | 6 | size_t fields_cap = 8; |
4441 | 6 | size_t fields_len = 0; |
4442 | 6 | LatValue *fields = malloc(fields_cap * sizeof(LatValue)); |
4443 | | |
4444 | 14 | for (;;) { |
4445 | | /* Parse one field */ |
4446 | 14 | size_t field_cap = 64; |
4447 | 14 | size_t field_len = 0; |
4448 | 14 | char *field = malloc(field_cap); |
4449 | | |
4450 | 14 | if (pos < input_len && input[pos] == '"') { |
4451 | | /* Quoted field */ |
4452 | 2 | pos++; /* skip opening quote */ |
4453 | 17 | for (;;) { |
4454 | 17 | if (pos >= input_len) break; |
4455 | 17 | if (input[pos] == '"') { |
4456 | 2 | if (pos + 1 < input_len && input[pos + 1] == '"') { |
4457 | | /* Escaped quote */ |
4458 | 0 | if (field_len + 1 >= field_cap) { field_cap *= 2; field = realloc(field, field_cap); } |
4459 | 0 | field[field_len++] = '"'; |
4460 | 0 | pos += 2; |
4461 | 2 | } else { |
4462 | | /* End of quoted field */ |
4463 | 2 | pos++; /* skip closing quote */ |
4464 | 2 | break; |
4465 | 2 | } |
4466 | 15 | } else { |
4467 | 15 | if (field_len + 1 >= field_cap) { field_cap *= 2; field = realloc(field, field_cap); } |
4468 | 15 | field[field_len++] = input[pos++]; |
4469 | 15 | } |
4470 | 17 | } |
4471 | 12 | } else { |
4472 | | /* Unquoted field */ |
4473 | 30 | while (pos < input_len && input[pos] != ',' && input[pos] != '\n' && input[pos] != '\r') { |
4474 | 18 | if (field_len + 1 >= field_cap) { field_cap *= 2; field = realloc(field, field_cap); } |
4475 | 18 | field[field_len++] = input[pos++]; |
4476 | 18 | } |
4477 | 12 | } |
4478 | | |
4479 | 14 | field[field_len] = '\0'; |
4480 | | |
4481 | | /* Add field to fields array */ |
4482 | 14 | if (fields_len >= fields_cap) { fields_cap *= 2; fields = realloc(fields, fields_cap * sizeof(LatValue)); } |
4483 | 14 | fields[fields_len++] = value_string_owned(field); |
4484 | | |
4485 | | /* Check what follows */ |
4486 | 14 | if (pos < input_len && input[pos] == ',') { |
4487 | 8 | pos++; /* skip comma, continue to next field */ |
4488 | 8 | } else { |
4489 | 6 | break; /* end of row */ |
4490 | 6 | } |
4491 | 14 | } |
4492 | | |
4493 | | /* Skip line ending */ |
4494 | 6 | if (pos < input_len && input[pos] == '\r') pos++; |
4495 | 6 | if (pos < input_len && input[pos] == '\n') pos++; |
4496 | | |
4497 | | /* Build row array and add to rows (value_array does shallow copy, so don't free elements) */ |
4498 | 6 | LatValue row = value_array(fields, fields_len); |
4499 | 6 | free(fields); |
4500 | | |
4501 | 6 | if (rows_len >= rows_cap) { rows_cap *= 2; rows = realloc(rows, rows_cap * sizeof(LatValue)); } |
4502 | 6 | rows[rows_len++] = row; |
4503 | 6 | } |
4504 | | |
4505 | 3 | LatValue result = value_array(rows, rows_len); |
4506 | 3 | free(rows); |
4507 | | |
4508 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4509 | 3 | free(args); |
4510 | 3 | return eval_ok(result); |
4511 | 3 | } |
4512 | | |
4513 | | /// @builtin csv_stringify(rows: Array) -> String |
4514 | | /// @category CSV |
4515 | | /// Convert an array of arrays into a CSV string. |
4516 | | /// @example csv_stringify([["a", "b"], ["1", "2"]]) // "a,b\n1,2\n" |
4517 | 1.02k | if (strcmp(fn_name, "csv_stringify") == 0) { |
4518 | 2 | if (argc != 1 || args[0].type != VAL_ARRAY) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("csv_stringify() expects (Array)")); } |
4519 | | |
4520 | 2 | LatValue *data = &args[0]; |
4521 | 2 | size_t out_cap = 256; |
4522 | 2 | size_t out_len = 0; |
4523 | 2 | char *out = malloc(out_cap); |
4524 | | |
4525 | 6 | for (size_t r = 0; r < data->as.array.len; r++) { |
4526 | 4 | LatValue *row = &data->as.array.elems[r]; |
4527 | 4 | if (row->type != VAL_ARRAY) { |
4528 | 0 | free(out); |
4529 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4530 | 0 | free(args); |
4531 | 0 | return eval_err(strdup("csv_stringify(): each row must be an Array")); |
4532 | 0 | } |
4533 | 12 | for (size_t c = 0; c < row->as.array.len; c++) { |
4534 | 8 | if (c > 0) { |
4535 | 4 | if (out_len + 1 >= out_cap) { out_cap *= 2; out = realloc(out, out_cap); } |
4536 | 4 | out[out_len++] = ','; |
4537 | 4 | } |
4538 | | |
4539 | 8 | char *field_str = value_display(&row->as.array.elems[c]); |
4540 | 8 | size_t flen = strlen(field_str); |
4541 | | |
4542 | | /* Check if quoting is needed */ |
4543 | 8 | bool needs_quote = false; |
4544 | 26 | for (size_t k = 0; k < flen; k++) { |
4545 | 18 | if (field_str[k] == ',' || field_str[k] == '"' || field_str[k] == '\n' || field_str[k] == '\r') { |
4546 | 0 | needs_quote = true; |
4547 | 0 | break; |
4548 | 0 | } |
4549 | 18 | } |
4550 | | |
4551 | 8 | if (needs_quote) { |
4552 | | /* Count quotes for escaped size */ |
4553 | 0 | size_t extra = 0; |
4554 | 0 | for (size_t k = 0; k < flen; k++) { |
4555 | 0 | if (field_str[k] == '"') extra++; |
4556 | 0 | } |
4557 | 0 | size_t needed = flen + extra + 2; /* +2 for surrounding quotes */ |
4558 | 0 | while (out_len + needed >= out_cap) { out_cap *= 2; out = realloc(out, out_cap); } |
4559 | 0 | out[out_len++] = '"'; |
4560 | 0 | for (size_t k = 0; k < flen; k++) { |
4561 | 0 | if (field_str[k] == '"') out[out_len++] = '"'; |
4562 | 0 | out[out_len++] = field_str[k]; |
4563 | 0 | } |
4564 | 0 | out[out_len++] = '"'; |
4565 | 8 | } else { |
4566 | 8 | while (out_len + flen >= out_cap) { out_cap *= 2; out = realloc(out, out_cap); } |
4567 | 8 | memcpy(out + out_len, field_str, flen); |
4568 | 8 | out_len += flen; |
4569 | 8 | } |
4570 | 8 | free(field_str); |
4571 | 8 | } |
4572 | | /* Append newline */ |
4573 | 4 | if (out_len + 1 >= out_cap) { out_cap *= 2; out = realloc(out, out_cap); } |
4574 | 4 | out[out_len++] = '\n'; |
4575 | 4 | } |
4576 | 2 | out[out_len] = '\0'; |
4577 | | |
4578 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4579 | 2 | free(args); |
4580 | 2 | return eval_ok(value_string_owned(out)); |
4581 | 2 | } |
4582 | | |
4583 | | /* ── TOML builtins ── */ |
4584 | | |
4585 | | /// @builtin toml_parse(s: String) -> Map |
4586 | | /// @category Data Formats |
4587 | | /// Parse a TOML string into a Lattice Map. |
4588 | | /// @example toml_parse("[server]\nhost = \"localhost\"\nport = 8080") |
4589 | 1.02k | if (strcmp(fn_name, "toml_parse") == 0) { |
4590 | 6 | if (argc != 1 || args[0].type != VAL_STR) { |
4591 | 2 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
4592 | 1 | return eval_err(strdup("toml_parse() expects (String)")); |
4593 | 1 | } |
4594 | 5 | char *terr = NULL; |
4595 | 5 | LatValue result = toml_ops_parse(args[0].as.str_val, &terr); |
4596 | 10 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
4597 | 5 | if (terr) return eval_err(terr); |
4598 | 5 | return eval_ok(result); |
4599 | 5 | } |
4600 | | |
4601 | | /// @builtin toml_stringify(val: Map) -> String |
4602 | | /// @category Data Formats |
4603 | | /// Serialize a Lattice Map to a TOML string. |
4604 | | /// @example toml_stringify({"host": "localhost", "port": 8080}) |
4605 | 1.02k | if (strcmp(fn_name, "toml_stringify") == 0) { |
4606 | 2 | if (argc != 1) { |
4607 | 0 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
4608 | 0 | return eval_err(strdup("toml_stringify() expects (Map)")); |
4609 | 0 | } |
4610 | 2 | char *terr = NULL; |
4611 | 2 | char *toml = toml_ops_stringify(&args[0], &terr); |
4612 | 4 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
4613 | 2 | if (terr) { free(toml); return eval_err(terr); } |
4614 | 1 | return eval_ok(value_string_owned(toml)); |
4615 | 2 | } |
4616 | | |
4617 | | /* ── YAML builtins ── */ |
4618 | | |
4619 | | /// @builtin yaml_parse(s: String) -> Map|Array |
4620 | | /// @category Data Formats |
4621 | | /// Parse a YAML string into a Lattice value. |
4622 | | /// @example yaml_parse("name: Alice\nage: 30") |
4623 | 1.01k | if (strcmp(fn_name, "yaml_parse") == 0) { |
4624 | 7 | if (argc != 1 || args[0].type != VAL_STR) { |
4625 | 2 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
4626 | 1 | return eval_err(strdup("yaml_parse() expects (String)")); |
4627 | 1 | } |
4628 | 6 | char *yerr = NULL; |
4629 | 6 | LatValue result = yaml_ops_parse(args[0].as.str_val, &yerr); |
4630 | 12 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
4631 | 6 | if (yerr) return eval_err(yerr); |
4632 | 6 | return eval_ok(result); |
4633 | 6 | } |
4634 | | |
4635 | | /// @builtin yaml_stringify(val: Map|Array) -> String |
4636 | | /// @category Data Formats |
4637 | | /// Serialize a Lattice value to a YAML string. |
4638 | | /// @example yaml_stringify({"name": "Alice", "age": 30}) |
4639 | 1.01k | if (strcmp(fn_name, "yaml_stringify") == 0) { |
4640 | 2 | if (argc != 1) { |
4641 | 0 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
4642 | 0 | return eval_err(strdup("yaml_stringify() expects (value)")); |
4643 | 0 | } |
4644 | 2 | if (args[0].type != VAL_MAP && args[0].type != VAL_ARRAY) { |
4645 | 2 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
4646 | 1 | return eval_err(strdup("yaml_stringify: value must be a Map or Array")); |
4647 | 1 | } |
4648 | 1 | char *yerr = NULL; |
4649 | 1 | char *yaml = yaml_ops_stringify(&args[0], &yerr); |
4650 | 2 | for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); |
4651 | 1 | if (yerr) { free(yaml); return eval_err(yerr); } |
4652 | 1 | return eval_ok(value_string_owned(yaml)); |
4653 | 1 | } |
4654 | | |
4655 | | /* ── Regex builtins ── */ |
4656 | | |
4657 | | /// @builtin regex_match(pattern: String, str: String) -> Bool |
4658 | | /// @category Regex |
4659 | | /// Test if a string matches a regular expression pattern. |
4660 | | /// @example regex_match("^[0-9]+$", "123") // true |
4661 | 1.00k | if (strcmp(fn_name, "regex_match") == 0) { |
4662 | 4 | if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("regex_match() expects (String pattern, String str)")); } |
4663 | 4 | char *rerr = NULL; |
4664 | 4 | LatValue result = regex_match(args[0].as.str_val, args[1].as.str_val, &rerr); |
4665 | 12 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4666 | 4 | free(args); |
4667 | 4 | if (rerr) return eval_err(rerr); |
4668 | 3 | return eval_ok(result); |
4669 | 4 | } |
4670 | | |
4671 | | /// @builtin regex_find_all(pattern: String, str: String) -> Array |
4672 | | /// @category Regex |
4673 | | /// Find all matches of a pattern in a string, returning an array. |
4674 | | /// @example regex_find_all("[0-9]+", "a1b2c3") // ["1", "2", "3"] |
4675 | 1.00k | if (strcmp(fn_name, "regex_find_all") == 0) { |
4676 | 3 | if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("regex_find_all() expects (String pattern, String str)")); } |
4677 | 3 | char *rerr = NULL; |
4678 | 3 | LatValue result = regex_find_all(args[0].as.str_val, args[1].as.str_val, &rerr); |
4679 | 9 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4680 | 3 | free(args); |
4681 | 3 | if (rerr) return eval_err(rerr); |
4682 | 3 | return eval_ok(result); |
4683 | 3 | } |
4684 | | |
4685 | | /// @builtin regex_replace(pattern: String, str: String, replacement: String) -> String |
4686 | | /// @category Regex |
4687 | | /// Replace all matches of a pattern in a string. |
4688 | | /// @example regex_replace("[0-9]", "a1b2", "X") // "aXbX" |
4689 | 1.00k | if (strcmp(fn_name, "regex_replace") == 0) { |
4690 | 4 | if (argc != 3 || args[0].type != VAL_STR || args[1].type != VAL_STR || args[2].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("regex_replace() expects (String pattern, String str, String replacement)")); } |
4691 | 4 | char *rerr = NULL; |
4692 | 4 | char *result = regex_replace(args[0].as.str_val, args[1].as.str_val, args[2].as.str_val, &rerr); |
4693 | 16 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4694 | 4 | free(args); |
4695 | 4 | if (rerr) return eval_err(rerr); |
4696 | 4 | return eval_ok(value_string_owned(result)); |
4697 | 4 | } |
4698 | | |
4699 | | /// @builtin format(fmt: String, args: Any...) -> String |
4700 | | /// @category String Formatting |
4701 | | /// Format a string with placeholders replaced by arguments. |
4702 | | /// @example format("{} is {}", "sky", "blue") // "sky is blue" |
4703 | 998 | if (strcmp(fn_name, "format") == 0) { |
4704 | 26 | if (argc < 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("format() expects (String fmt, ...)")); } |
4705 | 25 | char *ferr = NULL; |
4706 | 25 | char *result = format_string(args[0].as.str_val, args + 1, argc - 1, &ferr); |
4707 | 84 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4708 | 25 | free(args); |
4709 | 25 | if (ferr) return eval_err(ferr); |
4710 | 24 | return eval_ok(value_string_owned(result)); |
4711 | 25 | } |
4712 | | |
4713 | | /* ── Crypto builtins ── */ |
4714 | | |
4715 | | /// @builtin sha256(s: String) -> String |
4716 | | /// @category Crypto |
4717 | | /// Compute the SHA-256 hash of a string, returned as hex. |
4718 | | /// @example sha256("hello") // "2cf24dba..." |
4719 | 972 | if (strcmp(fn_name, "sha256") == 0) { |
4720 | 3 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("sha256() expects (String)")); } |
4721 | 2 | char *cerr = NULL; |
4722 | 2 | char *result = crypto_sha256(args[0].as.str_val, strlen(args[0].as.str_val), &cerr); |
4723 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4724 | 2 | free(args); |
4725 | 2 | if (cerr) return eval_err(cerr); |
4726 | 2 | return eval_ok(value_string_owned(result)); |
4727 | 2 | } |
4728 | | |
4729 | | /// @builtin md5(s: String) -> String |
4730 | | /// @category Crypto |
4731 | | /// Compute the MD5 hash of a string, returned as hex. |
4732 | | /// @example md5("hello") // "5d41402a..." |
4733 | 969 | if (strcmp(fn_name, "md5") == 0) { |
4734 | 3 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("md5() expects (String)")); } |
4735 | 2 | char *cerr = NULL; |
4736 | 2 | char *result = crypto_md5(args[0].as.str_val, strlen(args[0].as.str_val), &cerr); |
4737 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4738 | 2 | free(args); |
4739 | 2 | if (cerr) return eval_err(cerr); |
4740 | 2 | return eval_ok(value_string_owned(result)); |
4741 | 2 | } |
4742 | | |
4743 | | /// @builtin base64_encode(s: String) -> String |
4744 | | /// @category Crypto |
4745 | | /// Encode a string to Base64. |
4746 | | /// @example base64_encode("hello") // "aGVsbG8=" |
4747 | 966 | if (strcmp(fn_name, "base64_encode") == 0) { |
4748 | 9 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("base64_encode() expects (String)")); } |
4749 | 8 | char *result = crypto_base64_encode(args[0].as.str_val, strlen(args[0].as.str_val)); |
4750 | 16 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4751 | 8 | free(args); |
4752 | 8 | return eval_ok(value_string_owned(result)); |
4753 | 9 | } |
4754 | | |
4755 | | /// @builtin base64_decode(s: String) -> String |
4756 | | /// @category Crypto |
4757 | | /// Decode a Base64 string. |
4758 | | /// @example base64_decode("aGVsbG8=") // "hello" |
4759 | 957 | if (strcmp(fn_name, "base64_decode") == 0) { |
4760 | 6 | if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("base64_decode() expects (String)")); } |
4761 | 5 | char *cerr = NULL; |
4762 | 5 | size_t decoded_len = 0; |
4763 | 5 | char *result = crypto_base64_decode(args[0].as.str_val, strlen(args[0].as.str_val), &decoded_len, &cerr); |
4764 | 10 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4765 | 5 | free(args); |
4766 | 5 | if (cerr) return eval_err(cerr); |
4767 | 4 | return eval_ok(value_string_owned(result)); |
4768 | 5 | } |
4769 | | |
4770 | | /* ── Date/time formatting builtins ── */ |
4771 | | |
4772 | | /// @builtin time_format(epoch_ms: Int, fmt: String) -> String |
4773 | | /// @category Date & Time |
4774 | | /// Format a Unix timestamp (ms) using a strftime format string. |
4775 | | /// @example time_format(0, "%Y-%m-%d") // "1970-01-01" |
4776 | 951 | if (strcmp(fn_name, "time_format") == 0) { |
4777 | 5 | if (argc != 2 || args[0].type != VAL_INT || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("time_format() expects (Int epoch_ms, String fmt)")); } |
4778 | 4 | char *terr = NULL; |
4779 | 4 | char *result = datetime_format(args[0].as.int_val, args[1].as.str_val, &terr); |
4780 | 12 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4781 | 4 | free(args); |
4782 | 4 | if (terr) return eval_err(terr); |
4783 | 4 | return eval_ok(value_string_owned(result)); |
4784 | 4 | } |
4785 | | |
4786 | | /// @builtin time_parse(datetime: String, fmt: String) -> Int |
4787 | | /// @category Date & Time |
4788 | | /// Parse a datetime string into a Unix timestamp (ms). |
4789 | | /// @example time_parse("2024-01-01", "%Y-%m-%d") // 1704067200000 |
4790 | 946 | if (strcmp(fn_name, "time_parse") == 0) { |
4791 | 5 | if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("time_parse() expects (String datetime, String fmt)")); } |
4792 | 4 | char *terr = NULL; |
4793 | 4 | int64_t result = datetime_parse(args[0].as.str_val, args[1].as.str_val, &terr); |
4794 | 12 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4795 | 4 | free(args); |
4796 | 4 | if (terr) return eval_err(terr); |
4797 | 3 | return eval_ok(value_int(result)); |
4798 | 4 | } |
4799 | | |
4800 | | /* ── Assertion builtin ── */ |
4801 | | |
4802 | | /// @builtin assert(cond: Any, msg?: String) -> Unit |
4803 | | /// @category Core |
4804 | | /// Assert that a condition is truthy, or raise an error with an optional message. |
4805 | | /// @example assert(1 == 1, "math works") |
4806 | 941 | if (strcmp(fn_name, "assert") == 0) { |
4807 | 92 | if (argc < 1 || argc > 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("assert() expects 1 or 2 arguments")); } |
4808 | 92 | bool truthy = value_is_truthy(&args[0]); |
4809 | 92 | if (!truthy) { |
4810 | 3 | char *msg = NULL; |
4811 | 3 | if (argc == 2 && args[1].type == VAL_STR) |
4812 | 3 | msg = strdup(args[1].as.str_val); |
4813 | 0 | else |
4814 | 0 | msg = strdup("assertion failed"); |
4815 | 9 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4816 | 3 | free(args); |
4817 | 3 | return eval_err(msg); |
4818 | 3 | } |
4819 | 185 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4820 | 89 | free(args); |
4821 | 89 | return eval_ok(value_unit()); |
4822 | 92 | } |
4823 | | |
4824 | | /// @builtin debug_assert(cond: Any, msg?: String) -> Unit |
4825 | | /// @category Core |
4826 | | /// Assert that a condition is truthy (no-op when assertions are disabled via --no-assertions). |
4827 | | /// @example debug_assert(x > 0, "x must be positive") |
4828 | 849 | if (strcmp(fn_name, "debug_assert") == 0) { |
4829 | 2 | if (argc < 1 || argc > 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("debug_assert() expects 1 or 2 arguments")); } |
4830 | 2 | if (ev->assertions_enabled) { |
4831 | 2 | bool truthy = value_is_truthy(&args[0]); |
4832 | 2 | if (!truthy) { |
4833 | 1 | char *msg = NULL; |
4834 | 1 | if (argc == 2 && args[1].type == VAL_STR) |
4835 | 1 | msg = strdup(args[1].as.str_val); |
4836 | 0 | else |
4837 | 0 | msg = strdup("debug assertion failed"); |
4838 | 3 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4839 | 1 | free(args); |
4840 | 1 | return eval_err(msg); |
4841 | 1 | } |
4842 | 2 | } |
4843 | 3 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4844 | 1 | free(args); |
4845 | 1 | return eval_ok(value_unit()); |
4846 | 2 | } |
4847 | | |
4848 | | /* ── Functional programming builtins ── */ |
4849 | | |
4850 | | /// @builtin identity(val: Any) -> Any |
4851 | | /// @category Functional |
4852 | | /// Return the argument unchanged. |
4853 | | /// @example identity(42) // 42 |
4854 | 847 | if (strcmp(fn_name, "identity") == 0) { |
4855 | 2 | if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("identity() expects 1 argument")); } |
4856 | 2 | LatValue result = value_deep_clone(&args[0]); |
4857 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4858 | 2 | free(args); |
4859 | 2 | return eval_ok(result); |
4860 | 2 | } |
4861 | | |
4862 | | /// @builtin pipe(val: Any, fns: Closure...) -> Any |
4863 | | /// @category Functional |
4864 | | /// Thread a value through a series of functions left to right. |
4865 | | /// @example pipe(5, |x| { x * 2 }, |x| { x + 1 }) // 11 |
4866 | 845 | if (strcmp(fn_name, "pipe") == 0) { |
4867 | 2 | if (argc < 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("pipe() expects a value and at least one function")); } |
4868 | 7 | for (size_t i = 1; i < argc; i++) { |
4869 | 5 | if (args[i].type != VAL_CLOSURE) { |
4870 | 0 | char *err = NULL; |
4871 | 0 | (void)asprintf(&err, "pipe() argument %zu is not a function", i + 1); |
4872 | 0 | for (size_t j = 0; j < argc; j++) value_free(&args[j]); |
4873 | 0 | free(args); |
4874 | 0 | return eval_err(err); |
4875 | 0 | } |
4876 | 5 | } |
4877 | 2 | LatValue current = value_deep_clone(&args[0]); |
4878 | 7 | for (size_t i = 1; i < argc; i++) { |
4879 | 5 | LatValue call_arg = current; |
4880 | 5 | EvalResult r = call_closure(ev, |
4881 | 5 | args[i].as.closure.param_names, |
4882 | 5 | args[i].as.closure.param_count, |
4883 | 5 | args[i].as.closure.body, |
4884 | 5 | args[i].as.closure.captured_env, |
4885 | 5 | &call_arg, 1, |
4886 | 5 | args[i].as.closure.default_values, args[i].as.closure.has_variadic); |
4887 | 5 | if (!IS_OK(r)) { |
4888 | 0 | for (size_t j = 0; j < argc; j++) value_free(&args[j]); |
4889 | 0 | free(args); |
4890 | 0 | return r; |
4891 | 0 | } |
4892 | 5 | current = r.value; |
4893 | 5 | } |
4894 | 9 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4895 | 2 | free(args); |
4896 | 2 | return eval_ok(current); |
4897 | 2 | } |
4898 | | |
4899 | | /// @builtin compose(f: Closure, g: Closure) -> Closure |
4900 | | /// @category Functional |
4901 | | /// Compose two functions: compose(f, g)(x) calls f(g(x)). |
4902 | | /// @example compose(|x| { x + 1 }, |x| { x * 2 })(3) // 7 |
4903 | 843 | if (strcmp(fn_name, "compose") == 0) { |
4904 | 2 | if (argc != 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("compose() expects 2 arguments (both closures)")); } |
4905 | 2 | if (args[0].type != VAL_CLOSURE || args[1].type != VAL_CLOSURE) { |
4906 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4907 | 0 | free(args); |
4908 | 0 | return eval_err(strdup("compose() arguments must be closures")); |
4909 | 0 | } |
4910 | | /* Build closure |x| { __compose_f(__compose_g(x)) } |
4911 | | * where f = args[0], g = args[1] */ |
4912 | 2 | Env *cenv = env_clone(ev->env); |
4913 | 2 | env_push_scope(cenv); |
4914 | 2 | env_define(cenv, "__compose_f", value_deep_clone(&args[0])); |
4915 | 2 | env_define(cenv, "__compose_g", value_deep_clone(&args[1])); |
4916 | | |
4917 | | /* AST: __compose_f(__compose_g(x)) (intentionally leaked — borrowed by closure) */ |
4918 | 2 | Expr *x_var = expr_ident(strdup("x")); |
4919 | 2 | Expr **g_cargs = malloc(sizeof(Expr *)); |
4920 | 2 | g_cargs[0] = x_var; |
4921 | 2 | Expr *g_call = expr_call(expr_ident(strdup("__compose_g")), g_cargs, 1); |
4922 | 2 | Expr **f_cargs = malloc(sizeof(Expr *)); |
4923 | 2 | f_cargs[0] = g_call; |
4924 | 2 | Expr *body = expr_call(expr_ident(strdup("__compose_f")), f_cargs, 1); |
4925 | | |
4926 | 2 | char **params = malloc(sizeof(char *)); |
4927 | 2 | params[0] = strdup("x"); |
4928 | 2 | LatValue closure = value_closure(params, 1, body, cenv, NULL, false); |
4929 | | |
4930 | 6 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4931 | 2 | free(args); |
4932 | 2 | return eval_ok(closure); |
4933 | 2 | } |
4934 | | |
4935 | | /* ── Named function lookup ── */ |
4936 | 841 | FnDecl *fd_head = find_fn(ev, fn_name); |
4937 | 841 | if (fd_head) { |
4938 | 422 | FnDecl *fd = fd_head; |
4939 | 422 | if (fd_head->next_overload) { |
4940 | 3 | fd = resolve_overload(fd_head, args, argc); |
4941 | 3 | if (!fd) { |
4942 | 0 | char *err = NULL; |
4943 | 0 | (void)asprintf(&err, "no matching overload for '%s' with given argument phases", fn_name); |
4944 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4945 | 0 | free(args); |
4946 | 0 | return eval_err(err); |
4947 | 0 | } |
4948 | 3 | } |
4949 | | /* Allocate write-back slots for fluid parameters */ |
4950 | 422 | LatValue **writeback = calloc(argc, sizeof(LatValue *)); |
4951 | 422 | EvalResult res = call_fn(ev, fd, args, argc, writeback); |
4952 | | /* Write back fluid parameters to caller's env */ |
4953 | 422 | if (IS_OK(res)) { |
4954 | 1.13k | for (size_t i = 0; i < argc; i++) { |
4955 | 719 | if (writeback[i] && expr->as.call.args[i]->tag == EXPR_IDENT) { |
4956 | 4 | env_set(ev->env, expr->as.call.args[i]->as.str_val, *writeback[i]); |
4957 | 4 | free(writeback[i]); |
4958 | 4 | writeback[i] = NULL; |
4959 | 4 | } |
4960 | 719 | } |
4961 | 411 | } |
4962 | | /* Clean up any unused writebacks */ |
4963 | 1.15k | for (size_t i = 0; i < argc; i++) { |
4964 | 728 | if (writeback[i]) { |
4965 | 0 | value_free(writeback[i]); |
4966 | 0 | free(writeback[i]); |
4967 | 0 | } |
4968 | 728 | } |
4969 | 422 | free(writeback); |
4970 | 422 | free(args); |
4971 | 422 | return res; |
4972 | 422 | } |
4973 | 841 | } |
4974 | | /* Otherwise evaluate callee — re-protect args since eval may trigger GC */ |
4975 | 891 | for (size_t i = 0; i < argc; i++) GC_PUSH(ev, &args[i]); |
4976 | 419 | EvalResult callee_r = eval_expr(ev, expr->as.call.func); |
4977 | 419 | GC_POP_N(ev, argc); |
4978 | 419 | if (!IS_OK(callee_r)) { |
4979 | 4 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4980 | 2 | free(args); |
4981 | 2 | return callee_r; |
4982 | 2 | } |
4983 | 417 | if (callee_r.value.type != VAL_CLOSURE) { |
4984 | 0 | char *err = NULL; |
4985 | 0 | (void)asprintf(&err, "'%s' is not callable", value_type_name(&callee_r.value)); |
4986 | 0 | value_free(&callee_r.value); |
4987 | 0 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
4988 | 0 | free(args); |
4989 | 0 | return eval_err(err); |
4990 | 0 | } |
4991 | | /* Derive a name for stack traces */ |
4992 | 417 | const char *closure_name = (expr->as.call.func->tag == EXPR_IDENT) |
4993 | 417 | ? expr->as.call.func->as.str_val : "<closure>"; |
4994 | | /* Native closure dispatch */ |
4995 | 417 | if (callee_r.value.as.closure.native_fn && !callee_r.value.as.closure.body) { |
4996 | 69 | ev_push_frame(ev, closure_name); |
4997 | 69 | EvalResult res; |
4998 | 69 | if (callee_r.value.as.closure.default_values == (struct Expr **)(uintptr_t)0x1) { |
4999 | | /* VM-style native (VMNativeFn signature) — used by builtin modules */ |
5000 | 5 | typedef LatValue (*VMNativeFn)(LatValue *, int); |
5001 | 5 | VMNativeFn fn = (VMNativeFn)callee_r.value.as.closure.native_fn; |
5002 | | /* Ensure current_rt exists for native error reporting */ |
5003 | 5 | LatRuntime *prev_rt = lat_runtime_current(); |
5004 | 5 | LatRuntime tmp_rt; |
5005 | 5 | if (!prev_rt) { |
5006 | 5 | memset(&tmp_rt, 0, sizeof(tmp_rt)); |
5007 | 5 | lat_runtime_set_current(&tmp_rt); |
5008 | 5 | } |
5009 | 5 | LatRuntime *rt = lat_runtime_current(); |
5010 | 5 | LatValue result = fn(args, (int)argc); |
5011 | 5 | if (rt->error) { |
5012 | 0 | char *msg = rt->error; |
5013 | 0 | rt->error = NULL; |
5014 | 0 | value_free(&result); |
5015 | 0 | res = eval_err(msg); |
5016 | 5 | } else { |
5017 | 5 | res = eval_ok(result); |
5018 | 5 | } |
5019 | 5 | if (!prev_rt) lat_runtime_set_current(NULL); |
5020 | 64 | } else { |
5021 | | /* Extension native (LatExtFn signature) */ |
5022 | 64 | res = call_native_closure(ev, |
5023 | 64 | callee_r.value.as.closure.native_fn, args, argc); |
5024 | 64 | } |
5025 | 69 | if (!IS_ERR(res)) ev_pop_frame(ev); |
5026 | 176 | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
5027 | 69 | value_free(&callee_r.value); |
5028 | 69 | free(args); |
5029 | 69 | return res; |
5030 | 69 | } |
5031 | | /* Root callee and args so GC inside block-body closures won't sweep them */ |
5032 | 348 | GC_PUSH(ev, &callee_r.value); |
5033 | 711 | for (size_t i = 0; i < argc; i++) GC_PUSH(ev, &args[i]); |
5034 | 348 | ev_push_frame(ev, closure_name); |
5035 | 348 | EvalResult res = call_closure(ev, |
5036 | 348 | callee_r.value.as.closure.param_names, |
5037 | 348 | callee_r.value.as.closure.param_count, |
5038 | 348 | callee_r.value.as.closure.body, |
5039 | 348 | callee_r.value.as.closure.captured_env, |
5040 | 348 | args, argc, |
5041 | 348 | callee_r.value.as.closure.default_values, callee_r.value.as.closure.has_variadic); |
5042 | 348 | if (!IS_ERR(res)) ev_pop_frame(ev); |
5043 | 348 | GC_POP_N(ev, argc); |
5044 | 348 | GC_POP(ev); |
5045 | 348 | value_free(&callee_r.value); |
5046 | 348 | free(args); |
5047 | 348 | return res; |
5048 | 417 | } |
5049 | | |
5050 | 2.81k | case EXPR_METHOD_CALL: { |
5051 | | /* Handle .push() specially - needs to mutate the binding in env */ |
5052 | 2.81k | if (strcmp(expr->as.method_call.method, "push") == 0 && |
5053 | 2.81k | expr->as.method_call.object->tag == EXPR_IDENT && |
5054 | 2.81k | expr->as.method_call.arg_count == 1) { |
5055 | 134 | const char *var_name = expr->as.method_call.object->as.str_val; |
5056 | 134 | LatValue existing; |
5057 | 134 | if (!env_get(ev->env, var_name, &existing)) { |
5058 | 0 | char *err = NULL; |
5059 | 0 | (void)asprintf(&err, "undefined variable '%s'", var_name); |
5060 | 0 | return eval_err(err); |
5061 | 0 | } |
5062 | 134 | if (existing.type != VAL_ARRAY && existing.type != VAL_BUFFER && existing.type != VAL_REF) { |
5063 | 0 | value_free(&existing); |
5064 | 0 | return eval_err(strdup(".push() is not defined on non-array")); |
5065 | 0 | } |
5066 | | /* Ref/Buffer push: handle via resolve_lvalue path below */ |
5067 | 134 | if (existing.type == VAL_BUFFER || existing.type == VAL_REF) { |
5068 | 3 | value_free(&existing); |
5069 | 3 | goto buffer_mutating_methods; |
5070 | 3 | } |
5071 | 131 | if (value_is_crystal(&existing)) { |
5072 | 0 | value_free(&existing); |
5073 | 0 | return eval_err(strdup("cannot push to a crystal array")); |
5074 | 0 | } |
5075 | 131 | if (existing.phase == VTAG_SUBLIMATED) { |
5076 | 1 | value_free(&existing); |
5077 | 1 | return eval_err(strdup("cannot push to a sublimated array")); |
5078 | 1 | } |
5079 | 130 | { |
5080 | 130 | const char *pmode = find_pressure(ev, var_name); |
5081 | 130 | if (pressure_blocks_grow(pmode)) { |
5082 | 2 | value_free(&existing); |
5083 | 2 | char *err = NULL; |
5084 | 2 | (void)asprintf(&err, "pressurized (%s): cannot push to '%s'", pmode, var_name); |
5085 | 2 | return eval_err(err); |
5086 | 2 | } |
5087 | 130 | } |
5088 | 128 | GC_PUSH(ev, &existing); |
5089 | 128 | EvalResult ar = eval_expr(ev, expr->as.method_call.args[0]); |
5090 | 128 | GC_POP(ev); |
5091 | 128 | if (!IS_OK(ar)) { value_free(&existing); return ar; } |
5092 | | /* Grow the array */ |
5093 | 128 | if (existing.as.array.len >= existing.as.array.cap) { |
5094 | 7 | size_t old_cap = existing.as.array.cap; |
5095 | 7 | existing.as.array.cap = old_cap < 4 ? 4 : old_cap * 2; |
5096 | 7 | LatValue *new_buf = fluid_alloc(ev->heap->fluid, |
5097 | 7 | existing.as.array.cap * sizeof(LatValue)); |
5098 | 7 | memcpy(new_buf, existing.as.array.elems, old_cap * sizeof(LatValue)); |
5099 | 7 | if (!fluid_dealloc(ev->heap->fluid, existing.as.array.elems)) |
5100 | 0 | free(existing.as.array.elems); |
5101 | 7 | existing.as.array.elems = new_buf; |
5102 | 7 | } |
5103 | 128 | existing.as.array.elems[existing.as.array.len++] = ar.value; |
5104 | 128 | env_set(ev->env, var_name, existing); |
5105 | 128 | return eval_ok(value_unit()); |
5106 | 128 | } |
5107 | | /// @method Map.set(key: String, value: Any) -> Unit |
5108 | | /// @category Map Methods |
5109 | | /// Set a key-value pair in the map (mutates in place). |
5110 | | /// @example m.set("name", "Alice") |
5111 | | /* Handle .set() on maps - needs mutation like .push() */ |
5112 | 2.68k | if (strcmp(expr->as.method_call.method, "set") == 0 && |
5113 | 2.68k | expr->as.method_call.arg_count == 2) { |
5114 | 955 | char *lv_err = NULL; |
5115 | 955 | LatValue *map_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err); |
5116 | 955 | if (map_lv && map_lv->type == VAL_REF) map_lv = &map_lv->as.ref.ref->value; |
5117 | 955 | if (map_lv && map_lv->type == VAL_MAP) { |
5118 | 955 | if (map_lv->phase == VTAG_SUBLIMATED) |
5119 | 0 | return eval_err(strdup("cannot set on a sublimated map")); |
5120 | 955 | EvalResult kr = eval_expr(ev, expr->as.method_call.args[0]); |
5121 | 955 | if (!IS_OK(kr)) return kr; |
5122 | 955 | if (kr.value.type != VAL_STR) { |
5123 | 0 | value_free(&kr.value); |
5124 | 0 | return eval_err(strdup(".set() key must be a string")); |
5125 | 0 | } |
5126 | 955 | GC_PUSH(ev, &kr.value); |
5127 | 955 | EvalResult vr = eval_expr(ev, expr->as.method_call.args[1]); |
5128 | 955 | GC_POP(ev); |
5129 | 955 | if (!IS_OK(vr)) { value_free(&kr.value); return vr; } |
5130 | | /* Free old value if key exists */ |
5131 | 955 | LatValue *old = (LatValue *)lat_map_get(map_lv->as.map.map, kr.value.as.str_val); |
5132 | 955 | if (old) value_free(old); |
5133 | 955 | lat_map_set(map_lv->as.map.map, kr.value.as.str_val, &vr.value); |
5134 | 955 | value_free(&kr.value); |
5135 | 955 | return eval_ok(value_unit()); |
5136 | 955 | } |
5137 | 0 | if (lv_err) free(lv_err); |
5138 | 0 | } |
5139 | | /* Handle .pop() on arrays - needs mutation */ |
5140 | 1.72k | if (strcmp(expr->as.method_call.method, "pop") == 0 && |
5141 | 1.72k | expr->as.method_call.arg_count == 0) { |
5142 | 4 | char *lv_err = NULL; |
5143 | 4 | LatValue *arr_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err); |
5144 | 4 | if (arr_lv && arr_lv->type == VAL_REF) arr_lv = &arr_lv->as.ref.ref->value; |
5145 | 4 | if (arr_lv && arr_lv->type == VAL_ARRAY) { |
5146 | 4 | if (value_is_crystal(arr_lv)) |
5147 | 0 | return eval_err(strdup("cannot pop from a crystal array")); |
5148 | 4 | if (arr_lv->phase == VTAG_SUBLIMATED) |
5149 | 1 | return eval_err(strdup("cannot pop from a sublimated array")); |
5150 | 3 | { |
5151 | 3 | const char *vn = get_method_obj_varname(expr->as.method_call.object); |
5152 | 3 | if (vn) { |
5153 | 3 | const char *pmode = find_pressure(ev, vn); |
5154 | 3 | if (pressure_blocks_shrink(pmode)) { |
5155 | 1 | char *err = NULL; |
5156 | 1 | (void)asprintf(&err, "pressurized (%s): cannot pop from '%s'", pmode, vn); |
5157 | 1 | return eval_err(err); |
5158 | 1 | } |
5159 | 3 | } |
5160 | 3 | } |
5161 | 2 | if (arr_lv->as.array.len == 0) |
5162 | 0 | return eval_err(strdup("pop on empty array")); |
5163 | 2 | LatValue popped = arr_lv->as.array.elems[--arr_lv->as.array.len]; |
5164 | 2 | return eval_ok(popped); |
5165 | 2 | } |
5166 | 0 | if (lv_err) free(lv_err); |
5167 | 0 | } |
5168 | | /* Handle .insert() on arrays - needs mutation */ |
5169 | 1.72k | if (strcmp(expr->as.method_call.method, "insert") == 0 && |
5170 | 1.72k | expr->as.method_call.arg_count == 2) { |
5171 | 1 | char *lv_err = NULL; |
5172 | 1 | LatValue *arr_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err); |
5173 | 1 | if (arr_lv && arr_lv->type == VAL_REF) arr_lv = &arr_lv->as.ref.ref->value; |
5174 | 1 | if (arr_lv && arr_lv->type == VAL_ARRAY) { |
5175 | 1 | if (value_is_crystal(arr_lv)) |
5176 | 0 | return eval_err(strdup("cannot insert into a crystal array")); |
5177 | 1 | if (arr_lv->phase == VTAG_SUBLIMATED) |
5178 | 0 | return eval_err(strdup("cannot insert into a sublimated array")); |
5179 | 1 | { |
5180 | 1 | const char *vn = get_method_obj_varname(expr->as.method_call.object); |
5181 | 1 | if (vn) { |
5182 | 1 | const char *pmode = find_pressure(ev, vn); |
5183 | 1 | if (pressure_blocks_grow(pmode)) { |
5184 | 0 | char *err = NULL; |
5185 | 0 | (void)asprintf(&err, "pressurized (%s): cannot insert into '%s'", pmode, vn); |
5186 | 0 | return eval_err(err); |
5187 | 0 | } |
5188 | 1 | } |
5189 | 1 | } |
5190 | 1 | EvalResult ir = eval_expr(ev, expr->as.method_call.args[0]); |
5191 | 1 | if (!IS_OK(ir)) return ir; |
5192 | 1 | if (ir.value.type != VAL_INT) { |
5193 | 0 | value_free(&ir.value); |
5194 | 0 | return eval_err(strdup(".insert() index must be an integer")); |
5195 | 0 | } |
5196 | 1 | int64_t idx = ir.value.as.int_val; |
5197 | 1 | value_free(&ir.value); |
5198 | 1 | if (idx < 0 || (size_t)idx > arr_lv->as.array.len) |
5199 | 0 | return eval_err(strdup(".insert() index out of bounds")); |
5200 | 1 | EvalResult vr = eval_expr(ev, expr->as.method_call.args[1]); |
5201 | 1 | if (!IS_OK(vr)) return vr; |
5202 | | /* Grow if needed */ |
5203 | 1 | if (arr_lv->as.array.len >= arr_lv->as.array.cap) { |
5204 | 0 | size_t old_cap = arr_lv->as.array.cap; |
5205 | 0 | arr_lv->as.array.cap = old_cap < 4 ? 4 : old_cap * 2; |
5206 | 0 | LatValue *new_buf = fluid_alloc(ev->heap->fluid, |
5207 | 0 | arr_lv->as.array.cap * sizeof(LatValue)); |
5208 | 0 | memcpy(new_buf, arr_lv->as.array.elems, old_cap * sizeof(LatValue)); |
5209 | 0 | if (!fluid_dealloc(ev->heap->fluid, arr_lv->as.array.elems)) |
5210 | 0 | free(arr_lv->as.array.elems); |
5211 | 0 | arr_lv->as.array.elems = new_buf; |
5212 | 0 | } |
5213 | | /* Shift elements right */ |
5214 | 1 | memmove(&arr_lv->as.array.elems[(size_t)idx + 1], |
5215 | 1 | &arr_lv->as.array.elems[(size_t)idx], |
5216 | 1 | (arr_lv->as.array.len - (size_t)idx) * sizeof(LatValue)); |
5217 | 1 | arr_lv->as.array.elems[(size_t)idx] = vr.value; |
5218 | 1 | arr_lv->as.array.len++; |
5219 | 1 | return eval_ok(value_unit()); |
5220 | 1 | } |
5221 | 0 | if (lv_err) free(lv_err); |
5222 | 0 | } |
5223 | | /* Handle .remove_at() on arrays - needs mutation */ |
5224 | 1.72k | if (strcmp(expr->as.method_call.method, "remove_at") == 0 && |
5225 | 1.72k | expr->as.method_call.arg_count == 1) { |
5226 | 1 | char *lv_err = NULL; |
5227 | 1 | LatValue *arr_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err); |
5228 | 1 | if (arr_lv && arr_lv->type == VAL_REF) arr_lv = &arr_lv->as.ref.ref->value; |
5229 | 1 | if (arr_lv && arr_lv->type == VAL_ARRAY) { |
5230 | 1 | if (value_is_crystal(arr_lv)) |
5231 | 0 | return eval_err(strdup("cannot remove from a crystal array")); |
5232 | 1 | if (arr_lv->phase == VTAG_SUBLIMATED) |
5233 | 0 | return eval_err(strdup("cannot remove from a sublimated array")); |
5234 | 1 | { |
5235 | 1 | const char *vn = get_method_obj_varname(expr->as.method_call.object); |
5236 | 1 | if (vn) { |
5237 | 1 | const char *pmode = find_pressure(ev, vn); |
5238 | 1 | if (pressure_blocks_shrink(pmode)) { |
5239 | 0 | char *err = NULL; |
5240 | 0 | (void)asprintf(&err, "pressurized (%s): cannot remove from '%s'", pmode, vn); |
5241 | 0 | return eval_err(err); |
5242 | 0 | } |
5243 | 1 | } |
5244 | 1 | } |
5245 | 1 | EvalResult ir = eval_expr(ev, expr->as.method_call.args[0]); |
5246 | 1 | if (!IS_OK(ir)) return ir; |
5247 | 1 | if (ir.value.type != VAL_INT) { |
5248 | 0 | value_free(&ir.value); |
5249 | 0 | return eval_err(strdup(".remove_at() index must be an integer")); |
5250 | 0 | } |
5251 | 1 | int64_t idx = ir.value.as.int_val; |
5252 | 1 | value_free(&ir.value); |
5253 | 1 | if (idx < 0 || (size_t)idx >= arr_lv->as.array.len) |
5254 | 0 | return eval_err(strdup(".remove_at() index out of bounds")); |
5255 | 1 | LatValue removed = arr_lv->as.array.elems[(size_t)idx]; |
5256 | | /* Shift elements left */ |
5257 | 1 | memmove(&arr_lv->as.array.elems[(size_t)idx], |
5258 | 1 | &arr_lv->as.array.elems[(size_t)idx + 1], |
5259 | 1 | (arr_lv->as.array.len - (size_t)idx - 1) * sizeof(LatValue)); |
5260 | 1 | arr_lv->as.array.len--; |
5261 | 1 | return eval_ok(removed); |
5262 | 1 | } |
5263 | 0 | if (lv_err) free(lv_err); |
5264 | 0 | } |
5265 | | /* Handle .merge() on maps - needs mutation */ |
5266 | 1.72k | if (strcmp(expr->as.method_call.method, "merge") == 0 && |
5267 | 1.72k | expr->as.method_call.arg_count == 1) { |
5268 | 1 | char *lv_err = NULL; |
5269 | 1 | LatValue *map_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err); |
5270 | 1 | if (map_lv && map_lv->type == VAL_REF) map_lv = &map_lv->as.ref.ref->value; |
5271 | 1 | if (map_lv && map_lv->type == VAL_MAP) { |
5272 | 1 | if (map_lv->phase == VTAG_SUBLIMATED) |
5273 | 0 | return eval_err(strdup("cannot merge into a sublimated map")); |
5274 | 1 | { |
5275 | 1 | const char *vn = get_method_obj_varname(expr->as.method_call.object); |
5276 | 1 | if (vn) { |
5277 | 1 | const char *pmode = find_pressure(ev, vn); |
5278 | 1 | if (pressure_blocks_grow(pmode)) { |
5279 | 0 | char *err = NULL; |
5280 | 0 | (void)asprintf(&err, "pressurized (%s): cannot merge into '%s'", pmode, vn); |
5281 | 0 | return eval_err(err); |
5282 | 0 | } |
5283 | 1 | } |
5284 | 1 | } |
5285 | 1 | EvalResult mr = eval_expr(ev, expr->as.method_call.args[0]); |
5286 | 1 | if (!IS_OK(mr)) return mr; |
5287 | 1 | if (mr.value.type != VAL_MAP) { |
5288 | 0 | value_free(&mr.value); |
5289 | 0 | return eval_err(strdup(".merge() argument must be a Map")); |
5290 | 0 | } |
5291 | 1 | LatMap *other = mr.value.as.map.map; |
5292 | 17 | for (size_t i = 0; i < other->cap; i++) { |
5293 | 16 | if (other->entries[i].state == MAP_OCCUPIED) { |
5294 | 1 | LatValue cloned = value_deep_clone((LatValue *)other->entries[i].value); |
5295 | 1 | LatValue *old = (LatValue *)lat_map_get(map_lv->as.map.map, other->entries[i].key); |
5296 | 1 | if (old) value_free(old); |
5297 | 1 | lat_map_set(map_lv->as.map.map, other->entries[i].key, &cloned); |
5298 | 1 | } |
5299 | 16 | } |
5300 | 1 | value_free(&mr.value); |
5301 | 1 | return eval_ok(value_unit()); |
5302 | 1 | } |
5303 | 0 | if (lv_err) free(lv_err); |
5304 | 0 | } |
5305 | | /// @method Map.remove(key: String) -> Unit |
5306 | | /// @category Map Methods |
5307 | | /// Remove a key from the map (mutates in place). |
5308 | | /// @example m.remove("name") |
5309 | | /* Handle .remove() on maps - needs mutation */ |
5310 | 1.72k | if (strcmp(expr->as.method_call.method, "remove") == 0 && |
5311 | 1.72k | expr->as.method_call.arg_count == 1) { |
5312 | 2 | char *lv_err = NULL; |
5313 | 2 | LatValue *map_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err); |
5314 | 2 | if (map_lv && map_lv->type == VAL_REF) map_lv = &map_lv->as.ref.ref->value; |
5315 | 2 | if (map_lv && map_lv->type == VAL_MAP) { |
5316 | 1 | if (map_lv->phase == VTAG_SUBLIMATED) |
5317 | 0 | return eval_err(strdup("cannot remove from a sublimated map")); |
5318 | 1 | { |
5319 | 1 | const char *vn = get_method_obj_varname(expr->as.method_call.object); |
5320 | 1 | if (vn) { |
5321 | 1 | const char *pmode = find_pressure(ev, vn); |
5322 | 1 | if (pressure_blocks_shrink(pmode)) { |
5323 | 0 | char *err = NULL; |
5324 | 0 | (void)asprintf(&err, "pressurized (%s): cannot remove from '%s'", pmode, vn); |
5325 | 0 | return eval_err(err); |
5326 | 0 | } |
5327 | 1 | } |
5328 | 1 | } |
5329 | 1 | EvalResult kr = eval_expr(ev, expr->as.method_call.args[0]); |
5330 | 1 | if (!IS_OK(kr)) return kr; |
5331 | 1 | if (kr.value.type != VAL_STR) { |
5332 | 0 | value_free(&kr.value); |
5333 | 0 | return eval_err(strdup(".remove() key must be a string")); |
5334 | 0 | } |
5335 | | /* Free old value if key exists */ |
5336 | 1 | LatValue *old = (LatValue *)lat_map_get(map_lv->as.map.map, kr.value.as.str_val); |
5337 | 1 | if (old) value_free(old); |
5338 | 1 | lat_map_remove(map_lv->as.map.map, kr.value.as.str_val); |
5339 | 1 | value_free(&kr.value); |
5340 | 1 | return eval_ok(value_unit()); |
5341 | 1 | } |
5342 | 1 | if (lv_err) free(lv_err); |
5343 | 1 | } |
5344 | | |
5345 | | /// @method Set.add(value: Any) -> Unit |
5346 | | /// @category Set Methods |
5347 | | /// Add an element to the set (mutates in place). |
5348 | | /// @example s.add(42) |
5349 | 1.72k | if (strcmp(expr->as.method_call.method, "add") == 0 && |
5350 | 1.72k | expr->as.method_call.arg_count == 1) { |
5351 | 6 | char *lv_err = NULL; |
5352 | 6 | LatValue *set_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err); |
5353 | 6 | if (set_lv && set_lv->type == VAL_SET) { |
5354 | 5 | EvalResult vr = eval_expr(ev, expr->as.method_call.args[0]); |
5355 | 5 | if (!IS_OK(vr)) return vr; |
5356 | 5 | char *key = value_display(&vr.value); |
5357 | | /* If key already exists, free old value */ |
5358 | 5 | LatValue *old = (LatValue *)lat_map_get(set_lv->as.set.map, key); |
5359 | 5 | if (old) value_free(old); |
5360 | 5 | lat_map_set(set_lv->as.set.map, key, &vr.value); |
5361 | 5 | free(key); |
5362 | 5 | return eval_ok(value_unit()); |
5363 | 5 | } |
5364 | 1 | if (lv_err) free(lv_err); |
5365 | 1 | } |
5366 | | |
5367 | | /// @method Set.remove(value: Any) -> Unit |
5368 | | /// @category Set Methods |
5369 | | /// Remove an element from the set (mutates in place). |
5370 | | /// @example s.remove(42) |
5371 | 1.71k | if (strcmp(expr->as.method_call.method, "remove") == 0 && |
5372 | 1.71k | expr->as.method_call.arg_count == 1) { |
5373 | 1 | char *lv_err = NULL; |
5374 | 1 | LatValue *set_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err); |
5375 | 1 | if (set_lv && set_lv->type == VAL_SET) { |
5376 | 1 | EvalResult vr = eval_expr(ev, expr->as.method_call.args[0]); |
5377 | 1 | if (!IS_OK(vr)) return vr; |
5378 | 1 | char *key = value_display(&vr.value); |
5379 | 1 | LatValue *old = (LatValue *)lat_map_get(set_lv->as.set.map, key); |
5380 | 1 | if (old) value_free(old); |
5381 | 1 | lat_map_remove(set_lv->as.set.map, key); |
5382 | 1 | free(key); |
5383 | 1 | value_free(&vr.value); |
5384 | 1 | return eval_ok(value_unit()); |
5385 | 1 | } |
5386 | 0 | if (lv_err) free(lv_err); |
5387 | 0 | } |
5388 | | |
5389 | | /* ── Buffer mutating methods ── */ |
5390 | 1.71k | buffer_mutating_methods: |
5391 | 1.71k | if (strcmp(expr->as.method_call.method, "push") == 0 && |
5392 | 1.71k | expr->as.method_call.arg_count == 1) { |
5393 | 3 | char *lv_err = NULL; |
5394 | 3 | LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err); |
5395 | 3 | if (buf_lv && buf_lv->type == VAL_REF) buf_lv = &buf_lv->as.ref.ref->value; |
5396 | 3 | if (buf_lv && buf_lv->type == VAL_ARRAY) { |
5397 | | /* Ref-wrapped array push via resolve_lvalue */ |
5398 | 1 | if (value_is_crystal(buf_lv)) |
5399 | 0 | return eval_err(strdup("cannot push to a crystal array")); |
5400 | 1 | EvalResult ar = eval_expr(ev, expr->as.method_call.args[0]); |
5401 | 1 | if (!IS_OK(ar)) return ar; |
5402 | 1 | if (buf_lv->as.array.len >= buf_lv->as.array.cap) { |
5403 | 0 | size_t old_cap = buf_lv->as.array.cap; |
5404 | 0 | buf_lv->as.array.cap = old_cap < 4 ? 4 : old_cap * 2; |
5405 | 0 | buf_lv->as.array.elems = realloc(buf_lv->as.array.elems, buf_lv->as.array.cap * sizeof(LatValue)); |
5406 | 0 | } |
5407 | 1 | buf_lv->as.array.elems[buf_lv->as.array.len++] = ar.value; |
5408 | 1 | return eval_ok(value_unit()); |
5409 | 1 | } |
5410 | 2 | if (buf_lv && buf_lv->type == VAL_BUFFER) { |
5411 | 2 | EvalResult vr = eval_expr(ev, expr->as.method_call.args[0]); |
5412 | 2 | if (!IS_OK(vr)) return vr; |
5413 | 2 | uint8_t byte = (vr.value.type == VAL_INT) ? (uint8_t)(vr.value.as.int_val & 0xFF) : 0; |
5414 | 2 | value_free(&vr.value); |
5415 | 2 | if (buf_lv->as.buffer.len >= buf_lv->as.buffer.cap) { |
5416 | 0 | buf_lv->as.buffer.cap = buf_lv->as.buffer.cap ? buf_lv->as.buffer.cap * 2 : 8; |
5417 | 0 | buf_lv->as.buffer.data = realloc(buf_lv->as.buffer.data, buf_lv->as.buffer.cap); |
5418 | 0 | } |
5419 | 2 | buf_lv->as.buffer.data[buf_lv->as.buffer.len++] = byte; |
5420 | 2 | return eval_ok(value_unit()); |
5421 | 2 | } |
5422 | 0 | if (lv_err) free(lv_err); |
5423 | 0 | } |
5424 | 1.71k | if (strcmp(expr->as.method_call.method, "push_u16") == 0 && |
5425 | 1.71k | expr->as.method_call.arg_count == 1) { |
5426 | 1 | char *lv_err = NULL; |
5427 | 1 | LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err); |
5428 | 1 | if (buf_lv && buf_lv->type == VAL_BUFFER) { |
5429 | 1 | EvalResult vr = eval_expr(ev, expr->as.method_call.args[0]); |
5430 | 1 | if (!IS_OK(vr)) return vr; |
5431 | 1 | uint16_t v = (vr.value.type == VAL_INT) ? (uint16_t)(vr.value.as.int_val & 0xFFFF) : 0; |
5432 | 1 | value_free(&vr.value); |
5433 | 1 | while (buf_lv->as.buffer.len + 2 > buf_lv->as.buffer.cap) { |
5434 | 0 | buf_lv->as.buffer.cap = buf_lv->as.buffer.cap ? buf_lv->as.buffer.cap * 2 : 8; |
5435 | 0 | buf_lv->as.buffer.data = realloc(buf_lv->as.buffer.data, buf_lv->as.buffer.cap); |
5436 | 0 | } |
5437 | 1 | buf_lv->as.buffer.data[buf_lv->as.buffer.len++] = (uint8_t)(v & 0xFF); |
5438 | 1 | buf_lv->as.buffer.data[buf_lv->as.buffer.len++] = (uint8_t)((v >> 8) & 0xFF); |
5439 | 1 | return eval_ok(value_unit()); |
5440 | 1 | } |
5441 | 0 | if (lv_err) free(lv_err); |
5442 | 0 | } |
5443 | 1.71k | if (strcmp(expr->as.method_call.method, "push_u32") == 0 && |
5444 | 1.71k | expr->as.method_call.arg_count == 1) { |
5445 | 1 | char *lv_err = NULL; |
5446 | 1 | LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err); |
5447 | 1 | if (buf_lv && buf_lv->type == VAL_BUFFER) { |
5448 | 1 | EvalResult vr = eval_expr(ev, expr->as.method_call.args[0]); |
5449 | 1 | if (!IS_OK(vr)) return vr; |
5450 | 1 | uint32_t v = (vr.value.type == VAL_INT) ? (uint32_t)(vr.value.as.int_val & 0xFFFFFFFF) : 0; |
5451 | 1 | value_free(&vr.value); |
5452 | 1 | while (buf_lv->as.buffer.len + 4 > buf_lv->as.buffer.cap) { |
5453 | 0 | buf_lv->as.buffer.cap = buf_lv->as.buffer.cap ? buf_lv->as.buffer.cap * 2 : 8; |
5454 | 0 | buf_lv->as.buffer.data = realloc(buf_lv->as.buffer.data, buf_lv->as.buffer.cap); |
5455 | 0 | } |
5456 | 1 | buf_lv->as.buffer.data[buf_lv->as.buffer.len++] = (uint8_t)(v & 0xFF); |
5457 | 1 | buf_lv->as.buffer.data[buf_lv->as.buffer.len++] = (uint8_t)((v >> 8) & 0xFF); |
5458 | 1 | buf_lv->as.buffer.data[buf_lv->as.buffer.len++] = (uint8_t)((v >> 16) & 0xFF); |
5459 | 1 | buf_lv->as.buffer.data[buf_lv->as.buffer.len++] = (uint8_t)((v >> 24) & 0xFF); |
5460 | 1 | return eval_ok(value_unit()); |
5461 | 1 | } |
5462 | 0 | if (lv_err) free(lv_err); |
5463 | 0 | } |
5464 | 1.71k | if (strcmp(expr->as.method_call.method, "write_u8") == 0 && |
5465 | 1.71k | expr->as.method_call.arg_count == 2) { |
5466 | 0 | char *lv_err = NULL; |
5467 | 0 | LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err); |
5468 | 0 | if (buf_lv && buf_lv->type == VAL_BUFFER) { |
5469 | 0 | EvalResult ir = eval_expr(ev, expr->as.method_call.args[0]); |
5470 | 0 | if (!IS_OK(ir)) return ir; |
5471 | 0 | EvalResult vr = eval_expr(ev, expr->as.method_call.args[1]); |
5472 | 0 | if (!IS_OK(vr)) { value_free(&ir.value); return vr; } |
5473 | 0 | if (ir.value.type != VAL_INT || ir.value.as.int_val < 0 || (size_t)ir.value.as.int_val >= buf_lv->as.buffer.len) { |
5474 | 0 | value_free(&ir.value); value_free(&vr.value); |
5475 | 0 | return eval_err(strdup("Buffer.write_u8: index out of bounds")); |
5476 | 0 | } |
5477 | 0 | buf_lv->as.buffer.data[ir.value.as.int_val] = (uint8_t)(vr.value.as.int_val & 0xFF); |
5478 | 0 | value_free(&ir.value); value_free(&vr.value); |
5479 | 0 | return eval_ok(value_unit()); |
5480 | 0 | } |
5481 | 0 | if (lv_err) free(lv_err); |
5482 | 0 | } |
5483 | 1.71k | if (strcmp(expr->as.method_call.method, "write_u16") == 0 && |
5484 | 1.71k | expr->as.method_call.arg_count == 2) { |
5485 | 1 | char *lv_err = NULL; |
5486 | 1 | LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err); |
5487 | 1 | if (buf_lv && buf_lv->type == VAL_BUFFER) { |
5488 | 1 | EvalResult ir = eval_expr(ev, expr->as.method_call.args[0]); |
5489 | 1 | if (!IS_OK(ir)) return ir; |
5490 | 1 | EvalResult vr = eval_expr(ev, expr->as.method_call.args[1]); |
5491 | 1 | if (!IS_OK(vr)) { value_free(&ir.value); return vr; } |
5492 | 1 | size_t idx = (size_t)ir.value.as.int_val; |
5493 | 1 | if (ir.value.type != VAL_INT || idx + 2 > buf_lv->as.buffer.len) { |
5494 | 0 | value_free(&ir.value); value_free(&vr.value); |
5495 | 0 | return eval_err(strdup("Buffer.write_u16: index out of bounds")); |
5496 | 0 | } |
5497 | 1 | uint16_t v = (uint16_t)(vr.value.as.int_val & 0xFFFF); |
5498 | 1 | buf_lv->as.buffer.data[idx] = (uint8_t)(v & 0xFF); |
5499 | 1 | buf_lv->as.buffer.data[idx+1] = (uint8_t)((v >> 8) & 0xFF); |
5500 | 1 | value_free(&ir.value); value_free(&vr.value); |
5501 | 1 | return eval_ok(value_unit()); |
5502 | 1 | } |
5503 | 0 | if (lv_err) free(lv_err); |
5504 | 0 | } |
5505 | 1.71k | if (strcmp(expr->as.method_call.method, "write_u32") == 0 && |
5506 | 1.71k | expr->as.method_call.arg_count == 2) { |
5507 | 1 | char *lv_err = NULL; |
5508 | 1 | LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err); |
5509 | 1 | if (buf_lv && buf_lv->type == VAL_BUFFER) { |
5510 | 1 | EvalResult ir = eval_expr(ev, expr->as.method_call.args[0]); |
5511 | 1 | if (!IS_OK(ir)) return ir; |
5512 | 1 | EvalResult vr = eval_expr(ev, expr->as.method_call.args[1]); |
5513 | 1 | if (!IS_OK(vr)) { value_free(&ir.value); return vr; } |
5514 | 1 | size_t idx = (size_t)ir.value.as.int_val; |
5515 | 1 | if (ir.value.type != VAL_INT || idx + 4 > buf_lv->as.buffer.len) { |
5516 | 0 | value_free(&ir.value); value_free(&vr.value); |
5517 | 0 | return eval_err(strdup("Buffer.write_u32: index out of bounds")); |
5518 | 0 | } |
5519 | 1 | uint32_t v = (uint32_t)(vr.value.as.int_val & 0xFFFFFFFF); |
5520 | 1 | buf_lv->as.buffer.data[idx] = (uint8_t)(v & 0xFF); |
5521 | 1 | buf_lv->as.buffer.data[idx+1] = (uint8_t)((v >> 8) & 0xFF); |
5522 | 1 | buf_lv->as.buffer.data[idx+2] = (uint8_t)((v >> 16) & 0xFF); |
5523 | 1 | buf_lv->as.buffer.data[idx+3] = (uint8_t)((v >> 24) & 0xFF); |
5524 | 1 | value_free(&ir.value); value_free(&vr.value); |
5525 | 1 | return eval_ok(value_unit()); |
5526 | 1 | } |
5527 | 0 | if (lv_err) free(lv_err); |
5528 | 0 | } |
5529 | 1.71k | if (strcmp(expr->as.method_call.method, "clear") == 0 && |
5530 | 1.71k | expr->as.method_call.arg_count == 0) { |
5531 | 1 | char *lv_err = NULL; |
5532 | 1 | LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err); |
5533 | 1 | if (buf_lv && buf_lv->type == VAL_BUFFER) { |
5534 | 1 | buf_lv->as.buffer.len = 0; |
5535 | 1 | return eval_ok(value_unit()); |
5536 | 1 | } |
5537 | 0 | if (lv_err) free(lv_err); |
5538 | 0 | } |
5539 | 1.71k | if (strcmp(expr->as.method_call.method, "fill") == 0 && |
5540 | 1.71k | expr->as.method_call.arg_count == 1) { |
5541 | 1 | char *lv_err = NULL; |
5542 | 1 | LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err); |
5543 | 1 | if (buf_lv && buf_lv->type == VAL_BUFFER) { |
5544 | 1 | EvalResult vr = eval_expr(ev, expr->as.method_call.args[0]); |
5545 | 1 | if (!IS_OK(vr)) return vr; |
5546 | 1 | uint8_t byte = (vr.value.type == VAL_INT) ? (uint8_t)(vr.value.as.int_val & 0xFF) : 0; |
5547 | 1 | value_free(&vr.value); |
5548 | 1 | memset(buf_lv->as.buffer.data, byte, buf_lv->as.buffer.len); |
5549 | 1 | return eval_ok(value_unit()); |
5550 | 1 | } |
5551 | 0 | if (lv_err) free(lv_err); |
5552 | 0 | } |
5553 | 1.70k | if (strcmp(expr->as.method_call.method, "resize") == 0 && |
5554 | 1.70k | expr->as.method_call.arg_count == 1) { |
5555 | 1 | char *lv_err = NULL; |
5556 | 1 | LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err); |
5557 | 1 | if (buf_lv && buf_lv->type == VAL_BUFFER) { |
5558 | 1 | EvalResult vr = eval_expr(ev, expr->as.method_call.args[0]); |
5559 | 1 | if (!IS_OK(vr)) return vr; |
5560 | 1 | if (vr.value.type != VAL_INT || vr.value.as.int_val < 0) { |
5561 | 0 | value_free(&vr.value); |
5562 | 0 | return eval_ok(value_unit()); |
5563 | 0 | } |
5564 | 1 | size_t new_len = (size_t)vr.value.as.int_val; |
5565 | 1 | value_free(&vr.value); |
5566 | 1 | if (new_len > buf_lv->as.buffer.cap) { |
5567 | 0 | buf_lv->as.buffer.cap = new_len; |
5568 | 0 | buf_lv->as.buffer.data = realloc(buf_lv->as.buffer.data, buf_lv->as.buffer.cap); |
5569 | 0 | } |
5570 | 1 | if (new_len > buf_lv->as.buffer.len) |
5571 | 1 | memset(buf_lv->as.buffer.data + buf_lv->as.buffer.len, 0, new_len - buf_lv->as.buffer.len); |
5572 | 1 | buf_lv->as.buffer.len = new_len; |
5573 | 1 | return eval_ok(value_unit()); |
5574 | 1 | } |
5575 | 0 | if (lv_err) free(lv_err); |
5576 | 0 | } |
5577 | | |
5578 | 1.70k | EvalResult objr = eval_expr(ev, expr->as.method_call.object); |
5579 | 1.70k | if (!IS_OK(objr)) return objr; |
5580 | | /* Optional chaining: if receiver is nil, return nil */ |
5581 | 1.70k | if (expr->as.method_call.optional && objr.value.type == VAL_NIL) { |
5582 | 1 | value_free(&objr.value); |
5583 | 1 | return eval_ok(value_nil()); |
5584 | 1 | } |
5585 | 1.70k | GC_PUSH(ev, &objr.value); |
5586 | 1.70k | size_t argc = expr->as.method_call.arg_count; |
5587 | 1.70k | LatValue *args = malloc((argc < 1 ? 1 : argc) * sizeof(LatValue)); |
5588 | 3.33k | for (size_t i = 0; i < argc; i++) { |
5589 | 1.62k | EvalResult ar = eval_expr(ev, expr->as.method_call.args[i]); |
5590 | 1.62k | if (!IS_OK(ar)) { |
5591 | 0 | GC_POP_N(ev, i); /* args */ |
5592 | 0 | GC_POP(ev); /* objr */ |
5593 | 0 | for (size_t j = 0; j < i; j++) value_free(&args[j]); |
5594 | 0 | free(args); |
5595 | 0 | value_free(&objr.value); |
5596 | 0 | return ar; |
5597 | 0 | } |
5598 | 1.62k | args[i] = ar.value; |
5599 | 1.62k | GC_PUSH(ev, &args[i]); |
5600 | 1.62k | } |
5601 | 1.70k | EvalResult res = eval_method_call(ev, objr.value, expr->as.method_call.method, args, argc); |
5602 | 1.70k | GC_POP_N(ev, argc); /* args */ |
5603 | 1.70k | GC_POP(ev); /* objr */ |
5604 | 1.70k | value_free(&objr.value); |
5605 | 3.33k | for (size_t i = 0; i < argc; i++) value_free(&args[i]); |
5606 | 1.70k | free(args); |
5607 | 1.70k | return res; |
5608 | 1.70k | } |
5609 | | |
5610 | 267 | case EXPR_FIELD_ACCESS: { |
5611 | 267 | EvalResult objr = eval_expr(ev, expr->as.field_access.object); |
5612 | 267 | if (!IS_OK(objr)) return objr; |
5613 | | /* Optional chaining: if receiver is nil, return nil */ |
5614 | 267 | if (expr->as.field_access.optional && objr.value.type == VAL_NIL) { |
5615 | 5 | value_free(&objr.value); |
5616 | 5 | return eval_ok(value_nil()); |
5617 | 5 | } |
5618 | | /* Tuple numeric field access: tuple.0, tuple.1, etc. */ |
5619 | 262 | if (objr.value.type == VAL_TUPLE) { |
5620 | 6 | const char *field = expr->as.field_access.field; |
5621 | 6 | char *endptr; |
5622 | 6 | long idx = strtol(field, &endptr, 10); |
5623 | 6 | if (*endptr != '\0' || idx < 0) { |
5624 | 0 | char *err = NULL; |
5625 | 0 | (void)asprintf(&err, "tuple field must be a non-negative integer, got '%s'", field); |
5626 | 0 | value_free(&objr.value); |
5627 | 0 | return eval_err(err); |
5628 | 0 | } |
5629 | 6 | if ((size_t)idx >= objr.value.as.tuple.len) { |
5630 | 0 | char *err = NULL; |
5631 | 0 | (void)asprintf(&err, "tuple index %ld out of bounds (len=%zu)", |
5632 | 0 | idx, objr.value.as.tuple.len); |
5633 | 0 | value_free(&objr.value); |
5634 | 0 | return eval_err(err); |
5635 | 0 | } |
5636 | 6 | LatValue result = value_deep_clone(&objr.value.as.tuple.elems[idx]); |
5637 | 6 | value_free(&objr.value); |
5638 | 6 | return eval_ok(result); |
5639 | 6 | } |
5640 | 256 | if (objr.value.type == VAL_MAP) { |
5641 | 3 | const char *field = expr->as.field_access.field; |
5642 | 3 | LatValue *val = (LatValue *)lat_map_get(objr.value.as.map.map, field); |
5643 | 3 | if (val) { |
5644 | 2 | LatValue result = value_deep_clone(val); |
5645 | 2 | value_free(&objr.value); |
5646 | 2 | return eval_ok(result); |
5647 | 2 | } |
5648 | 1 | char *err = NULL; |
5649 | 1 | (void)asprintf(&err, "map has no key '%s'", field); |
5650 | 1 | value_free(&objr.value); |
5651 | 1 | return eval_err(err); |
5652 | 3 | } |
5653 | 253 | if (objr.value.type != VAL_STRUCT) { |
5654 | 0 | char *err = NULL; |
5655 | 0 | (void)asprintf(&err, "cannot access field '%s' on %s", |
5656 | 0 | expr->as.field_access.field, value_type_name(&objr.value)); |
5657 | 0 | value_free(&objr.value); |
5658 | 0 | return eval_err(err); |
5659 | 0 | } |
5660 | 323 | for (size_t i = 0; i < objr.value.as.strct.field_count; i++) { |
5661 | 323 | if (strcmp(objr.value.as.strct.field_names[i], expr->as.field_access.field) == 0) { |
5662 | 253 | LatValue result = value_deep_clone(&objr.value.as.strct.field_values[i]); |
5663 | 253 | value_free(&objr.value); |
5664 | 253 | return eval_ok(result); |
5665 | 253 | } |
5666 | 323 | } |
5667 | 0 | char *err = NULL; |
5668 | 0 | (void)asprintf(&err, "struct has no field '%s'", expr->as.field_access.field); |
5669 | 0 | value_free(&objr.value); |
5670 | 0 | return eval_err(err); |
5671 | 253 | } |
5672 | | |
5673 | 314 | case EXPR_INDEX: { |
5674 | 314 | EvalResult objr = eval_expr(ev, expr->as.index.object); |
5675 | 314 | if (!IS_OK(objr)) return objr; |
5676 | | /* Optional chaining: if receiver is nil, return nil */ |
5677 | 314 | if (expr->as.index.optional && objr.value.type == VAL_NIL) { |
5678 | 1 | value_free(&objr.value); |
5679 | 1 | return eval_ok(value_nil()); |
5680 | 1 | } |
5681 | 313 | GC_PUSH(ev, &objr.value); |
5682 | 313 | EvalResult idxr = eval_expr(ev, expr->as.index.index); |
5683 | 313 | GC_POP(ev); |
5684 | 313 | if (!IS_OK(idxr)) { value_free(&objr.value); return idxr; } |
5685 | 313 | if (objr.value.type == VAL_ARRAY && idxr.value.type == VAL_INT) { |
5686 | 237 | size_t idx = (size_t)idxr.value.as.int_val; |
5687 | 237 | value_free(&idxr.value); |
5688 | 237 | if (idx >= objr.value.as.array.len) { |
5689 | 0 | char *err = NULL; |
5690 | 0 | (void)asprintf(&err, "index %zu out of bounds (length %zu)", |
5691 | 0 | idx, objr.value.as.array.len); |
5692 | 0 | value_free(&objr.value); |
5693 | 0 | return eval_err(err); |
5694 | 0 | } |
5695 | 237 | LatValue result = value_deep_clone(&objr.value.as.array.elems[idx]); |
5696 | 237 | value_free(&objr.value); |
5697 | 237 | return eval_ok(result); |
5698 | 237 | } |
5699 | 76 | if (objr.value.type == VAL_STR && idxr.value.type == VAL_INT) { |
5700 | 3 | size_t idx = (size_t)idxr.value.as.int_val; |
5701 | 3 | value_free(&idxr.value); |
5702 | 3 | size_t slen = strlen(objr.value.as.str_val); |
5703 | 3 | if (idx >= slen) { |
5704 | 0 | char *err = NULL; |
5705 | 0 | (void)asprintf(&err, "string index %zu out of bounds (length %zu)", idx, slen); |
5706 | 0 | value_free(&objr.value); |
5707 | 0 | return eval_err(err); |
5708 | 0 | } |
5709 | 3 | char buf[2] = { objr.value.as.str_val[idx], '\0' }; |
5710 | 3 | value_free(&objr.value); |
5711 | 3 | return eval_ok(value_string(buf)); |
5712 | 3 | } |
5713 | | /* String slicing with range */ |
5714 | 73 | if (objr.value.type == VAL_STR && idxr.value.type == VAL_RANGE) { |
5715 | 2 | char *sliced = lat_str_substring(objr.value.as.str_val, |
5716 | 2 | idxr.value.as.range.start, |
5717 | 2 | idxr.value.as.range.end); |
5718 | 2 | value_free(&objr.value); |
5719 | 2 | value_free(&idxr.value); |
5720 | 2 | return eval_ok(value_string_owned(sliced)); |
5721 | 2 | } |
5722 | | /* Map indexing: map["key"] */ |
5723 | 71 | if (objr.value.type == VAL_MAP && idxr.value.type == VAL_STR) { |
5724 | 38 | LatValue *found = (LatValue *)lat_map_get(objr.value.as.map.map, idxr.value.as.str_val); |
5725 | 38 | LatValue result = found ? value_deep_clone(found) : value_unit(); |
5726 | 38 | value_free(&objr.value); |
5727 | 38 | value_free(&idxr.value); |
5728 | 38 | return eval_ok(result); |
5729 | 38 | } |
5730 | | /* Buffer indexing: buf[i] -> Int (0-255) */ |
5731 | 33 | if (objr.value.type == VAL_BUFFER && idxr.value.type == VAL_INT) { |
5732 | 28 | size_t bidx = (size_t)idxr.value.as.int_val; |
5733 | 28 | value_free(&idxr.value); |
5734 | 28 | if (bidx >= objr.value.as.buffer.len) { |
5735 | 0 | char *berr = NULL; |
5736 | 0 | (void)asprintf(&berr, "buffer index %zu out of bounds (length %zu)", |
5737 | 0 | bidx, objr.value.as.buffer.len); |
5738 | 0 | value_free(&objr.value); |
5739 | 0 | return eval_err(berr); |
5740 | 0 | } |
5741 | 28 | LatValue result = value_int(objr.value.as.buffer.data[bidx]); |
5742 | 28 | value_free(&objr.value); |
5743 | 28 | return eval_ok(result); |
5744 | 28 | } |
5745 | | /* Ref proxy: delegate indexing to inner value */ |
5746 | 5 | if (objr.value.type == VAL_REF) { |
5747 | 5 | LatValue *inner = &objr.value.as.ref.ref->value; |
5748 | 5 | if (inner->type == VAL_MAP && idxr.value.type == VAL_STR) { |
5749 | 3 | LatValue *found = (LatValue *)lat_map_get(inner->as.map.map, idxr.value.as.str_val); |
5750 | 3 | LatValue result = found ? value_deep_clone(found) : value_unit(); |
5751 | 3 | value_free(&objr.value); |
5752 | 3 | value_free(&idxr.value); |
5753 | 3 | return eval_ok(result); |
5754 | 3 | } |
5755 | 2 | if (inner->type == VAL_ARRAY && idxr.value.type == VAL_INT) { |
5756 | 2 | size_t idx = (size_t)idxr.value.as.int_val; |
5757 | 2 | value_free(&idxr.value); |
5758 | 2 | if (idx >= inner->as.array.len) { |
5759 | 0 | char *berr = NULL; |
5760 | 0 | (void)asprintf(&berr, "index %zu out of bounds (length %zu)", |
5761 | 0 | idx, inner->as.array.len); |
5762 | 0 | value_free(&objr.value); |
5763 | 0 | return eval_err(berr); |
5764 | 0 | } |
5765 | 2 | LatValue result = value_deep_clone(&inner->as.array.elems[idx]); |
5766 | 2 | value_free(&objr.value); |
5767 | 2 | return eval_ok(result); |
5768 | 2 | } |
5769 | 2 | } |
5770 | 0 | char *err = NULL; |
5771 | 0 | (void)asprintf(&err, "cannot index %s with %s", |
5772 | 0 | value_type_name(&objr.value), value_type_name(&idxr.value)); |
5773 | 0 | value_free(&objr.value); |
5774 | 0 | value_free(&idxr.value); |
5775 | 0 | return eval_err(err); |
5776 | 5 | } |
5777 | | |
5778 | 3.67k | case EXPR_ARRAY: { |
5779 | 3.67k | size_t n = expr->as.array.count; |
5780 | 3.67k | size_t cap = n > 0 ? n : 4; |
5781 | 3.67k | size_t out = 0; |
5782 | 3.67k | LatValue *elems = malloc(cap * sizeof(LatValue)); |
5783 | 3.67k | size_t gc_count = 0; |
5784 | 12.6k | for (size_t i = 0; i < n; i++) { |
5785 | 9.00k | if (expr->as.array.elems[i]->tag == EXPR_SPREAD) { |
5786 | 9 | EvalResult er = eval_expr(ev, expr->as.array.elems[i]->as.spread_expr); |
5787 | 9 | if (!IS_OK(er)) { |
5788 | 0 | GC_POP_N(ev, gc_count); |
5789 | 0 | for (size_t j = 0; j < out; j++) value_free(&elems[j]); |
5790 | 0 | free(elems); |
5791 | 0 | return er; |
5792 | 0 | } |
5793 | 9 | if (er.value.type != VAL_ARRAY) { |
5794 | 0 | char *msg = NULL; |
5795 | 0 | (void)asprintf(&msg, "cannot spread non-array value of type %s", |
5796 | 0 | value_type_name(&er.value)); |
5797 | 0 | GC_POP_N(ev, gc_count); |
5798 | 0 | for (size_t j = 0; j < out; j++) value_free(&elems[j]); |
5799 | 0 | free(elems); |
5800 | 0 | value_free(&er.value); |
5801 | 0 | return eval_err(msg); |
5802 | 0 | } |
5803 | 9 | size_t slen = er.value.as.array.len; |
5804 | 15 | while (out + slen > cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); } |
5805 | 27 | for (size_t j = 0; j < slen; j++) { |
5806 | 18 | elems[out] = value_deep_clone(&er.value.as.array.elems[j]); |
5807 | 18 | GC_PUSH(ev, &elems[out]); |
5808 | 18 | gc_count++; |
5809 | 18 | out++; |
5810 | 18 | } |
5811 | 9 | value_free(&er.value); |
5812 | 8.99k | } else { |
5813 | 8.99k | EvalResult er = eval_expr(ev, expr->as.array.elems[i]); |
5814 | 8.99k | if (!IS_OK(er)) { |
5815 | 0 | GC_POP_N(ev, gc_count); |
5816 | 0 | for (size_t j = 0; j < out; j++) value_free(&elems[j]); |
5817 | 0 | free(elems); |
5818 | 0 | return er; |
5819 | 0 | } |
5820 | 8.99k | if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); } |
5821 | 8.99k | elems[out] = er.value; |
5822 | 8.99k | GC_PUSH(ev, &elems[out]); |
5823 | 8.99k | gc_count++; |
5824 | 8.99k | out++; |
5825 | 8.99k | } |
5826 | 9.00k | } |
5827 | 3.67k | GC_POP_N(ev, gc_count); |
5828 | 3.67k | stats_array(&ev->stats); |
5829 | 3.67k | LatValue arr = value_array(elems, out); |
5830 | 3.67k | free(elems); |
5831 | 3.67k | return eval_ok(arr); |
5832 | 3.67k | } |
5833 | | |
5834 | 11 | case EXPR_TUPLE: { |
5835 | 11 | size_t n = expr->as.tuple.count; |
5836 | 11 | LatValue *elems = malloc(n * sizeof(LatValue)); |
5837 | 42 | for (size_t i = 0; i < n; i++) { |
5838 | 31 | EvalResult er = eval_expr(ev, expr->as.tuple.elems[i]); |
5839 | 31 | if (!IS_OK(er)) { |
5840 | 0 | GC_POP_N(ev, i); |
5841 | 0 | for (size_t j = 0; j < i; j++) value_free(&elems[j]); |
5842 | 0 | free(elems); |
5843 | 0 | return er; |
5844 | 0 | } |
5845 | 31 | elems[i] = er.value; |
5846 | 31 | GC_PUSH(ev, &elems[i]); |
5847 | 31 | } |
5848 | 11 | GC_POP_N(ev, n); |
5849 | 11 | LatValue tup = value_tuple(elems, n); |
5850 | 11 | free(elems); |
5851 | 11 | return eval_ok(tup); |
5852 | 11 | } |
5853 | | |
5854 | 194 | case EXPR_STRUCT_LIT: { |
5855 | 194 | const char *sname = expr->as.struct_lit.name; |
5856 | 194 | size_t fc = expr->as.struct_lit.field_count; |
5857 | | /* Validate fields if struct def is registered */ |
5858 | 194 | StructDecl *sd = find_struct(ev, sname); |
5859 | 194 | if (sd) { |
5860 | 578 | for (size_t i = 0; i < fc; i++) { |
5861 | 384 | bool found = false; |
5862 | 578 | for (size_t j = 0; j < sd->field_count; j++) { |
5863 | 578 | if (strcmp(expr->as.struct_lit.fields[i].name, sd->fields[j].name) == 0) { |
5864 | 384 | found = true; break; |
5865 | 384 | } |
5866 | 578 | } |
5867 | 384 | if (!found) { |
5868 | 0 | char *err = NULL; |
5869 | 0 | (void)asprintf(&err, "struct '%s' has no field '%s'", |
5870 | 0 | sname, expr->as.struct_lit.fields[i].name); |
5871 | 0 | return eval_err(err); |
5872 | 0 | } |
5873 | 384 | } |
5874 | 194 | } |
5875 | 194 | char **names = malloc(fc * sizeof(char *)); |
5876 | 194 | LatValue *vals = malloc(fc * sizeof(LatValue)); |
5877 | 578 | for (size_t i = 0; i < fc; i++) { |
5878 | 384 | names[i] = expr->as.struct_lit.fields[i].name; |
5879 | 384 | EvalResult er = eval_expr(ev, expr->as.struct_lit.fields[i].value); |
5880 | 384 | if (!IS_OK(er)) { |
5881 | 0 | GC_POP_N(ev, i); |
5882 | 0 | for (size_t j = 0; j < i; j++) value_free(&vals[j]); |
5883 | 0 | free(names); free(vals); |
5884 | 0 | return er; |
5885 | 0 | } |
5886 | 384 | vals[i] = er.value; |
5887 | 384 | GC_PUSH(ev, &vals[i]); |
5888 | 384 | } |
5889 | 194 | GC_POP_N(ev, fc); |
5890 | 194 | stats_struct(&ev->stats); |
5891 | 194 | LatValue st = value_struct(sname, names, vals, fc); |
5892 | 194 | free(names); free(vals); |
5893 | | /* Alloy enforcement: apply per-field phase from struct declaration */ |
5894 | 194 | if (sd) { |
5895 | 194 | bool has_phase_decl = false; |
5896 | 572 | for (size_t i = 0; i < sd->field_count; i++) { |
5897 | 381 | if (sd->fields[i].ty.phase != PHASE_UNSPECIFIED) { has_phase_decl = true; break; } |
5898 | 381 | } |
5899 | 194 | if (has_phase_decl) { |
5900 | 3 | st.as.strct.field_phases = calloc(st.as.strct.field_count, sizeof(PhaseTag)); |
5901 | 9 | for (size_t i = 0; i < st.as.strct.field_count; i++) { |
5902 | | /* Find matching decl field */ |
5903 | 9 | for (size_t j = 0; j < sd->field_count; j++) { |
5904 | 9 | if (strcmp(st.as.strct.field_names[i], sd->fields[j].name) == 0) { |
5905 | 6 | if (sd->fields[j].ty.phase == PHASE_CRYSTAL) { |
5906 | 3 | st.as.strct.field_values[i] = value_freeze(st.as.strct.field_values[i]); |
5907 | 3 | st.as.strct.field_phases[i] = VTAG_CRYSTAL; |
5908 | 3 | } else if (sd->fields[j].ty.phase == PHASE_FLUID) { |
5909 | 3 | st.as.strct.field_phases[i] = VTAG_FLUID; |
5910 | 3 | } else { |
5911 | 0 | st.as.strct.field_phases[i] = st.phase; |
5912 | 0 | } |
5913 | 6 | break; |
5914 | 6 | } |
5915 | 9 | } |
5916 | 6 | } |
5917 | 3 | } |
5918 | 194 | } |
5919 | 194 | return eval_ok(st); |
5920 | 194 | } |
5921 | | |
5922 | | /// @builtin freeze(val: Any) -> Any |
5923 | | /// @category Phase Transitions |
5924 | | /// Transition a value to the crystal (immutable) phase. |
5925 | | /// @example freeze([1, 2, 3]) // crystal [1, 2, 3] |
5926 | 317 | case EXPR_FREEZE: { |
5927 | 317 | stats_freeze(&ev->stats); |
5928 | | |
5929 | | /* Partial crystallization: freeze(s.field) or freeze(m["key"]) */ |
5930 | 317 | if (expr->as.freeze.expr->tag == EXPR_FIELD_ACCESS) { |
5931 | 3 | char *lv_err = NULL; |
5932 | | /* Resolve parent struct */ |
5933 | 3 | LatValue *parent = resolve_lvalue(ev, expr->as.freeze.expr->as.field_access.object, &lv_err); |
5934 | 3 | if (!parent) return eval_err(lv_err); |
5935 | 3 | if (parent->type != VAL_STRUCT) { |
5936 | 0 | return eval_err(strdup("partial freeze requires a struct")); |
5937 | 0 | } |
5938 | 3 | if (parent->phase == VTAG_CRYSTAL) { |
5939 | 0 | return eval_ok(value_deep_clone(parent)); /* already fully frozen */ |
5940 | 0 | } |
5941 | 3 | const char *fname = expr->as.freeze.expr->as.field_access.field; |
5942 | 3 | size_t fi = (size_t)-1; |
5943 | 3 | for (size_t i = 0; i < parent->as.strct.field_count; i++) { |
5944 | 3 | if (strcmp(parent->as.strct.field_names[i], fname) == 0) { fi = i; break; } |
5945 | 3 | } |
5946 | 3 | if (fi == (size_t)-1) { |
5947 | 0 | char *err = NULL; |
5948 | 0 | (void)asprintf(&err, "struct has no field '%s'", fname); |
5949 | 0 | return eval_err(err); |
5950 | 0 | } |
5951 | | /* Run contract if present */ |
5952 | 3 | if (expr->as.freeze.contract) { |
5953 | 1 | EvalResult cr = eval_expr(ev, expr->as.freeze.contract); |
5954 | 1 | if (!IS_OK(cr)) return cr; |
5955 | 1 | LatValue check_val = value_deep_clone(&parent->as.strct.field_values[fi]); |
5956 | 1 | EvalResult vr = call_closure(ev, cr.value.as.closure.param_names, |
5957 | 1 | cr.value.as.closure.param_count, cr.value.as.closure.body, |
5958 | 1 | cr.value.as.closure.captured_env, &check_val, 1, |
5959 | 1 | cr.value.as.closure.default_values, cr.value.as.closure.has_variadic); |
5960 | 1 | value_free(&cr.value); |
5961 | 1 | if (!IS_OK(vr)) { |
5962 | 0 | char *msg = NULL; |
5963 | 0 | (void)asprintf(&msg, "freeze contract failed: %s", vr.error); |
5964 | 0 | free(vr.error); |
5965 | 0 | return eval_err(msg); |
5966 | 0 | } |
5967 | 1 | value_free(&vr.value); |
5968 | 1 | } |
5969 | | /* Freeze the field value */ |
5970 | 3 | parent->as.strct.field_values[fi] = value_freeze(parent->as.strct.field_values[fi]); |
5971 | | /* Lazy-allocate field_phases */ |
5972 | 3 | if (!parent->as.strct.field_phases) { |
5973 | 3 | parent->as.strct.field_phases = calloc(parent->as.strct.field_count, sizeof(PhaseTag)); |
5974 | 3 | } |
5975 | 3 | parent->as.strct.field_phases[fi] = VTAG_CRYSTAL; |
5976 | 3 | return eval_ok(value_deep_clone(&parent->as.strct.field_values[fi])); |
5977 | 3 | } |
5978 | 314 | if (expr->as.freeze.expr->tag == EXPR_INDEX) { |
5979 | 2 | const Expr *idx_expr = expr->as.freeze.expr; |
5980 | | /* Evaluate key first */ |
5981 | 2 | EvalResult kr = eval_expr(ev, idx_expr->as.index.index); |
5982 | 2 | if (!IS_OK(kr)) return kr; |
5983 | 2 | if (kr.value.type != VAL_STR) { |
5984 | 0 | value_free(&kr.value); |
5985 | 0 | return eval_err(strdup("partial freeze: map key must be a string")); |
5986 | 0 | } |
5987 | 2 | char *key = strdup(kr.value.as.str_val); |
5988 | 2 | value_free(&kr.value); |
5989 | | /* Resolve parent map */ |
5990 | 2 | char *lv_err = NULL; |
5991 | 2 | LatValue *parent = resolve_lvalue(ev, idx_expr->as.index.object, &lv_err); |
5992 | 2 | if (!parent) { free(key); return eval_err(lv_err); } |
5993 | 2 | if (parent->type != VAL_MAP) { |
5994 | 0 | free(key); |
5995 | 0 | return eval_err(strdup("partial freeze requires a map")); |
5996 | 0 | } |
5997 | 2 | if (parent->phase == VTAG_CRYSTAL) { |
5998 | 0 | free(key); |
5999 | 0 | return eval_ok(value_deep_clone(parent)); |
6000 | 0 | } |
6001 | 2 | LatValue *val_ptr = (LatValue *)lat_map_get(parent->as.map.map, key); |
6002 | 2 | if (!val_ptr) { |
6003 | 0 | char *err = NULL; |
6004 | 0 | (void)asprintf(&err, "map has no key '%s'", key); |
6005 | 0 | free(key); |
6006 | 0 | return eval_err(err); |
6007 | 0 | } |
6008 | | /* Run contract if present */ |
6009 | 2 | if (expr->as.freeze.contract) { |
6010 | 0 | EvalResult cr = eval_expr(ev, expr->as.freeze.contract); |
6011 | 0 | if (!IS_OK(cr)) { free(key); return cr; } |
6012 | 0 | LatValue check_val = value_deep_clone(val_ptr); |
6013 | 0 | EvalResult vr = call_closure(ev, cr.value.as.closure.param_names, |
6014 | 0 | cr.value.as.closure.param_count, cr.value.as.closure.body, |
6015 | 0 | cr.value.as.closure.captured_env, &check_val, 1, |
6016 | 0 | cr.value.as.closure.default_values, cr.value.as.closure.has_variadic); |
6017 | 0 | value_free(&cr.value); |
6018 | 0 | if (!IS_OK(vr)) { |
6019 | 0 | char *msg = NULL; |
6020 | 0 | (void)asprintf(&msg, "freeze contract failed: %s", vr.error); |
6021 | 0 | free(vr.error); |
6022 | 0 | free(key); |
6023 | 0 | return eval_err(msg); |
6024 | 0 | } |
6025 | 0 | value_free(&vr.value); |
6026 | 0 | } |
6027 | | /* Freeze the value at this key */ |
6028 | 2 | *val_ptr = value_freeze(*val_ptr); |
6029 | | /* Lazy-allocate key_phases */ |
6030 | 2 | if (!parent->as.map.key_phases) { |
6031 | 2 | parent->as.map.key_phases = calloc(1, sizeof(LatMap)); |
6032 | 2 | *parent->as.map.key_phases = lat_map_new(sizeof(PhaseTag)); |
6033 | 2 | } |
6034 | 2 | PhaseTag crystal = VTAG_CRYSTAL; |
6035 | 2 | lat_map_set(parent->as.map.key_phases, key, &crystal); |
6036 | 2 | LatValue ret = value_deep_clone(val_ptr); |
6037 | 2 | free(key); |
6038 | 2 | return eval_ok(ret); |
6039 | 2 | } |
6040 | | |
6041 | 312 | if (expr->as.freeze.expr->tag == EXPR_IDENT) { |
6042 | 277 | const char *name = expr->as.freeze.expr->as.str_val; |
6043 | 277 | if (ev->mode == MODE_STRICT) { |
6044 | | /* Strict mode: consuming freeze removes the binding */ |
6045 | 3 | LatValue val; |
6046 | 3 | if (!env_remove(ev->env, name, &val)) { |
6047 | 0 | char *err = NULL; |
6048 | 0 | (void)asprintf(&err, "undefined variable '%s'", name); |
6049 | 0 | return eval_err(err); |
6050 | 0 | } |
6051 | 3 | if (val.type == VAL_CHANNEL) { |
6052 | 0 | value_free(&val); |
6053 | 0 | return eval_err(strdup("cannot freeze a Channel")); |
6054 | 0 | } |
6055 | | /* Run crystallization contract if present */ |
6056 | 3 | if (expr->as.freeze.contract) { |
6057 | 0 | EvalResult cr = eval_expr(ev, expr->as.freeze.contract); |
6058 | 0 | if (!IS_OK(cr)) { value_free(&val); return cr; } |
6059 | 0 | LatValue check_val = value_deep_clone(&val); |
6060 | 0 | EvalResult vr = call_closure(ev, cr.value.as.closure.param_names, |
6061 | 0 | cr.value.as.closure.param_count, cr.value.as.closure.body, |
6062 | 0 | cr.value.as.closure.captured_env, &check_val, 1, |
6063 | 0 | cr.value.as.closure.default_values, cr.value.as.closure.has_variadic); |
6064 | 0 | value_free(&cr.value); |
6065 | 0 | if (!IS_OK(vr)) { |
6066 | 0 | char *msg = NULL; |
6067 | 0 | (void)asprintf(&msg, "freeze contract failed: %s", vr.error); |
6068 | 0 | free(vr.error); |
6069 | 0 | value_free(&val); |
6070 | 0 | return eval_err(msg); |
6071 | 0 | } |
6072 | 0 | value_free(&vr.value); |
6073 | 0 | } |
6074 | 3 | uint64_t ft0 = now_ns(); |
6075 | 3 | val = value_freeze(val); |
6076 | 3 | freeze_to_region(ev, &val); |
6077 | 3 | ev->stats.freeze_total_ns += now_ns() - ft0; |
6078 | 3 | record_history(ev, name); |
6079 | 3 | { |
6080 | 3 | char *cascade_err = freeze_cascade(ev, name); |
6081 | 3 | if (cascade_err) { value_free(&val); return eval_err(cascade_err); } |
6082 | 3 | } |
6083 | 3 | EvalResult fr = fire_reactions(ev, name, "crystal"); |
6084 | 3 | if (!IS_OK(fr)) { value_free(&val); return fr; } |
6085 | 3 | return eval_ok(val); |
6086 | 3 | } |
6087 | | /* Casual mode: freeze the binding in-place */ |
6088 | 274 | LatValue val; |
6089 | 274 | if (!env_get(ev->env, name, &val)) { |
6090 | 0 | char *err = NULL; |
6091 | 0 | (void)asprintf(&err, "undefined variable '%s'", name); |
6092 | 0 | return eval_err(err); |
6093 | 0 | } |
6094 | 274 | if (val.type == VAL_CHANNEL) { |
6095 | 1 | value_free(&val); |
6096 | 1 | return eval_err(strdup("cannot freeze a Channel")); |
6097 | 1 | } |
6098 | | /* Freeze-except: selectively freeze struct fields/map keys */ |
6099 | 273 | if (expr->as.freeze.except_count > 0) { |
6100 | | /* Evaluate except field names */ |
6101 | 3 | char **except_names = malloc(expr->as.freeze.except_count * sizeof(char *)); |
6102 | 6 | for (size_t i = 0; i < expr->as.freeze.except_count; i++) { |
6103 | 3 | EvalResult er = eval_expr(ev, expr->as.freeze.except_fields[i]); |
6104 | 3 | if (!IS_OK(er)) { |
6105 | 0 | for (size_t j = 0; j < i; j++) free(except_names[j]); |
6106 | 0 | free(except_names); |
6107 | 0 | value_free(&val); |
6108 | 0 | return er; |
6109 | 0 | } |
6110 | 3 | if (er.value.type != VAL_STR) { |
6111 | 0 | for (size_t j = 0; j < i; j++) free(except_names[j]); |
6112 | 0 | free(except_names); |
6113 | 0 | value_free(&val); |
6114 | 0 | value_free(&er.value); |
6115 | 0 | return eval_err(strdup("freeze except: field names must be strings")); |
6116 | 0 | } |
6117 | 3 | except_names[i] = strdup(er.value.as.str_val); |
6118 | 3 | value_free(&er.value); |
6119 | 3 | } |
6120 | 3 | if (val.type == VAL_STRUCT) { |
6121 | | /* Lazy-allocate field_phases */ |
6122 | 2 | if (!val.as.strct.field_phases) { |
6123 | 2 | val.as.strct.field_phases = calloc(val.as.strct.field_count, sizeof(PhaseTag)); |
6124 | 8 | for (size_t i = 0; i < val.as.strct.field_count; i++) |
6125 | 6 | val.as.strct.field_phases[i] = val.phase; |
6126 | 2 | } |
6127 | 8 | for (size_t i = 0; i < val.as.strct.field_count; i++) { |
6128 | 6 | bool exempted = false; |
6129 | 10 | for (size_t j = 0; j < expr->as.freeze.except_count; j++) { |
6130 | 6 | if (strcmp(val.as.strct.field_names[i], except_names[j]) == 0) { |
6131 | 2 | exempted = true; break; |
6132 | 2 | } |
6133 | 6 | } |
6134 | 6 | if (!exempted) { |
6135 | 4 | val.as.strct.field_values[i] = value_freeze(val.as.strct.field_values[i]); |
6136 | 4 | val.as.strct.field_phases[i] = VTAG_CRYSTAL; |
6137 | 4 | } else { |
6138 | 2 | val.as.strct.field_phases[i] = VTAG_FLUID; |
6139 | 2 | } |
6140 | 6 | } |
6141 | 2 | } else if (val.type == VAL_MAP) { |
6142 | | /* Lazy-allocate key_phases */ |
6143 | 1 | if (!val.as.map.key_phases) { |
6144 | 1 | val.as.map.key_phases = calloc(1, sizeof(LatMap)); |
6145 | 1 | *val.as.map.key_phases = lat_map_new(sizeof(PhaseTag)); |
6146 | 1 | } |
6147 | 17 | for (size_t i = 0; i < val.as.map.map->cap; i++) { |
6148 | 16 | if (val.as.map.map->entries[i].state != MAP_OCCUPIED) continue; |
6149 | 3 | const char *key = val.as.map.map->entries[i].key; |
6150 | 3 | bool exempted = false; |
6151 | 5 | for (size_t j = 0; j < expr->as.freeze.except_count; j++) { |
6152 | 3 | if (strcmp(key, except_names[j]) == 0) { |
6153 | 1 | exempted = true; break; |
6154 | 1 | } |
6155 | 3 | } |
6156 | 3 | PhaseTag phase; |
6157 | 3 | if (!exempted) { |
6158 | 2 | LatValue *vp = (LatValue *)val.as.map.map->entries[i].value; |
6159 | 2 | *vp = value_freeze(*vp); |
6160 | 2 | phase = VTAG_CRYSTAL; |
6161 | 2 | } else { |
6162 | 1 | phase = VTAG_FLUID; |
6163 | 1 | } |
6164 | 3 | lat_map_set(val.as.map.key_phases, key, &phase); |
6165 | 3 | } |
6166 | 1 | } else { |
6167 | 0 | for (size_t j = 0; j < expr->as.freeze.except_count; j++) free(except_names[j]); |
6168 | 0 | free(except_names); |
6169 | 0 | value_free(&val); |
6170 | 0 | return eval_err(strdup("freeze except requires a struct or map")); |
6171 | 0 | } |
6172 | 6 | for (size_t j = 0; j < expr->as.freeze.except_count; j++) free(except_names[j]); |
6173 | 3 | free(except_names); |
6174 | 3 | LatValue ret = value_deep_clone(&val); |
6175 | 3 | env_set(ev->env, name, val); |
6176 | 3 | record_history(ev, name); |
6177 | 3 | return eval_ok(ret); |
6178 | 3 | } |
6179 | | /* Validate pending seed contracts */ |
6180 | 270 | for (size_t si = 0; si < ev->seed_count; si++) { |
6181 | 1 | if (strcmp(ev->seeds[si].var_name, name) == 0) { |
6182 | 1 | LatValue check_val = value_deep_clone(&val); |
6183 | 1 | EvalResult vr = call_closure(ev, |
6184 | 1 | ev->seeds[si].contract.as.closure.param_names, |
6185 | 1 | ev->seeds[si].contract.as.closure.param_count, |
6186 | 1 | ev->seeds[si].contract.as.closure.body, |
6187 | 1 | ev->seeds[si].contract.as.closure.captured_env, |
6188 | 1 | &check_val, 1, |
6189 | 1 | ev->seeds[si].contract.as.closure.default_values, |
6190 | 1 | ev->seeds[si].contract.as.closure.has_variadic); |
6191 | 1 | if (!IS_OK(vr)) { |
6192 | 0 | char *msg = NULL; |
6193 | 0 | (void)asprintf(&msg, "seed contract failed on freeze: %s", vr.error); |
6194 | 0 | free(vr.error); |
6195 | 0 | value_free(&val); |
6196 | 0 | return eval_err(msg); |
6197 | 0 | } |
6198 | 1 | if (!value_is_truthy(&vr.value)) { |
6199 | 1 | value_free(&vr.value); |
6200 | 1 | value_free(&val); |
6201 | 1 | return eval_err(strdup("seed contract failed on freeze: contract returned false")); |
6202 | 1 | } |
6203 | 0 | value_free(&vr.value); |
6204 | 0 | } |
6205 | 1 | } |
6206 | | /* Run crystallization contract if present */ |
6207 | 269 | if (expr->as.freeze.contract) { |
6208 | 6 | EvalResult cr = eval_expr(ev, expr->as.freeze.contract); |
6209 | 6 | if (!IS_OK(cr)) { value_free(&val); return cr; } |
6210 | 6 | LatValue check_val = value_deep_clone(&val); |
6211 | 6 | EvalResult vr = call_closure(ev, cr.value.as.closure.param_names, |
6212 | 6 | cr.value.as.closure.param_count, cr.value.as.closure.body, |
6213 | 6 | cr.value.as.closure.captured_env, &check_val, 1, |
6214 | 6 | cr.value.as.closure.default_values, cr.value.as.closure.has_variadic); |
6215 | 6 | value_free(&cr.value); |
6216 | 6 | if (!IS_OK(vr)) { |
6217 | 3 | char *msg = NULL; |
6218 | 3 | (void)asprintf(&msg, "freeze contract failed: %s", vr.error); |
6219 | 3 | free(vr.error); |
6220 | 3 | value_free(&val); |
6221 | 3 | return eval_err(msg); |
6222 | 3 | } |
6223 | 3 | value_free(&vr.value); |
6224 | 3 | } |
6225 | 266 | uint64_t ft0 = now_ns(); |
6226 | 266 | val = value_freeze(val); |
6227 | 266 | freeze_to_region(ev, &val); |
6228 | 266 | ev->stats.freeze_total_ns += now_ns() - ft0; |
6229 | 266 | LatValue ret = value_deep_clone(&val); |
6230 | 266 | env_set(ev->env, name, val); |
6231 | 266 | record_history(ev, name); |
6232 | 266 | { |
6233 | 266 | char *cascade_err = freeze_cascade(ev, name); |
6234 | 266 | if (cascade_err) { value_free(&ret); return eval_err(cascade_err); } |
6235 | 266 | } |
6236 | 265 | EvalResult fr = fire_reactions(ev, name, "crystal"); |
6237 | 265 | if (!IS_OK(fr)) { value_free(&ret); return fr; } |
6238 | 264 | return eval_ok(ret); |
6239 | 265 | } |
6240 | 35 | EvalResult er = eval_expr(ev, expr->as.freeze.expr); |
6241 | 35 | if (!IS_OK(er)) return er; |
6242 | 35 | if (er.value.type == VAL_CHANNEL) { |
6243 | 0 | value_free(&er.value); |
6244 | 0 | return eval_err(strdup("cannot freeze a Channel")); |
6245 | 0 | } |
6246 | | /* Run crystallization contract if present */ |
6247 | 35 | if (expr->as.freeze.contract) { |
6248 | 1 | EvalResult cr = eval_expr(ev, expr->as.freeze.contract); |
6249 | 1 | if (!IS_OK(cr)) { value_free(&er.value); return cr; } |
6250 | 1 | LatValue check_val = value_deep_clone(&er.value); |
6251 | 1 | EvalResult vr = call_closure(ev, cr.value.as.closure.param_names, |
6252 | 1 | cr.value.as.closure.param_count, cr.value.as.closure.body, |
6253 | 1 | cr.value.as.closure.captured_env, &check_val, 1, |
6254 | 1 | cr.value.as.closure.default_values, cr.value.as.closure.has_variadic); |
6255 | 1 | value_free(&cr.value); |
6256 | 1 | if (!IS_OK(vr)) { |
6257 | 0 | char *msg = NULL; |
6258 | 0 | (void)asprintf(&msg, "freeze contract failed: %s", vr.error); |
6259 | 0 | free(vr.error); |
6260 | 0 | value_free(&er.value); |
6261 | 0 | return eval_err(msg); |
6262 | 0 | } |
6263 | 1 | value_free(&vr.value); |
6264 | 1 | } |
6265 | 35 | { uint64_t ft0 = now_ns(); |
6266 | 35 | er.value = value_freeze(er.value); |
6267 | 35 | freeze_to_region(ev, &er.value); |
6268 | 35 | ev->stats.freeze_total_ns += now_ns() - ft0; } |
6269 | 35 | return eval_ok(er.value); |
6270 | 35 | } |
6271 | | |
6272 | | /// @builtin thaw(val: Any) -> Any |
6273 | | /// @category Phase Transitions |
6274 | | /// Transition a crystal value back to the flux (mutable) phase. |
6275 | | /// @example thaw(freeze([1, 2])) // flux [1, 2] |
6276 | 189 | case EXPR_THAW: { |
6277 | 189 | stats_thaw(&ev->stats); |
6278 | 189 | if (expr->as.freeze_expr->tag == EXPR_IDENT) { |
6279 | 189 | const char *name = expr->as.freeze_expr->as.str_val; |
6280 | | /* Thaw the binding in-place */ |
6281 | 189 | LatValue val; |
6282 | 189 | if (!env_get(ev->env, name, &val)) { |
6283 | 0 | char *err = NULL; |
6284 | 0 | (void)asprintf(&err, "undefined variable '%s'", name); |
6285 | 0 | return eval_err(err); |
6286 | 0 | } |
6287 | 189 | uint64_t tt0 = now_ns(); |
6288 | 189 | LatValue thawed = value_thaw(&val); |
6289 | 189 | ev->stats.thaw_total_ns += now_ns() - tt0; |
6290 | 189 | value_free(&val); |
6291 | 189 | LatValue ret = value_deep_clone(&thawed); |
6292 | 189 | env_set(ev->env, name, thawed); |
6293 | 189 | record_history(ev, name); |
6294 | 189 | EvalResult fr = fire_reactions(ev, name, "fluid"); |
6295 | 189 | if (!IS_OK(fr)) { value_free(&ret); return fr; } |
6296 | 189 | return eval_ok(ret); |
6297 | 189 | } |
6298 | 0 | EvalResult er = eval_expr(ev, expr->as.freeze_expr); |
6299 | 0 | if (!IS_OK(er)) return er; |
6300 | 0 | { uint64_t tt0 = now_ns(); |
6301 | 0 | LatValue thawed = value_thaw(&er.value); |
6302 | 0 | ev->stats.thaw_total_ns += now_ns() - tt0; |
6303 | 0 | value_free(&er.value); |
6304 | 0 | return eval_ok(thawed); } |
6305 | 0 | } |
6306 | | |
6307 | | /// @builtin clone(val: Any) -> Any |
6308 | | /// @category Phase Transitions |
6309 | | /// Create a deep copy of a value. |
6310 | | /// @example clone(my_array) // independent copy |
6311 | 3 | case EXPR_CLONE: { |
6312 | 3 | stats_deep_clone(&ev->stats); |
6313 | 3 | EvalResult er = eval_expr(ev, expr->as.freeze_expr); |
6314 | 3 | if (!IS_OK(er)) return er; |
6315 | 3 | LatValue cloned = value_deep_clone(&er.value); |
6316 | 3 | value_free(&er.value); |
6317 | 3 | return eval_ok(cloned); |
6318 | 3 | } |
6319 | | |
6320 | | /// @builtin anneal(val) |transform| { ... } -> Any |
6321 | | /// @category Phase Transitions |
6322 | | /// Atomically thaw a crystal value, apply a transformation, and refreeze. |
6323 | | /// @example anneal(frozen_map) |m| { m["key"] = "value"; m } |
6324 | 7 | case EXPR_ANNEAL: { |
6325 | 7 | stats_thaw(&ev->stats); |
6326 | 7 | stats_freeze(&ev->stats); |
6327 | | |
6328 | | /* Evaluate the closure expression */ |
6329 | 7 | EvalResult clr = eval_expr(ev, expr->as.anneal.closure); |
6330 | 7 | if (!IS_OK(clr)) return clr; |
6331 | | |
6332 | | /* Special handling for identifier targets (in-place update) */ |
6333 | 7 | if (expr->as.anneal.expr->tag == EXPR_IDENT) { |
6334 | 6 | const char *name = expr->as.anneal.expr->as.str_val; |
6335 | 6 | LatValue val; |
6336 | 6 | if (!env_get(ev->env, name, &val)) { |
6337 | 0 | value_free(&clr.value); |
6338 | 0 | char *err = NULL; |
6339 | 0 | (void)asprintf(&err, "undefined variable '%s'", name); |
6340 | 0 | return eval_err(err); |
6341 | 0 | } |
6342 | 6 | if (val.phase != VTAG_CRYSTAL) { |
6343 | 1 | value_free(&val); |
6344 | 1 | value_free(&clr.value); |
6345 | 1 | return eval_err(strdup("anneal requires a crystal value")); |
6346 | 1 | } |
6347 | | /* Thaw */ |
6348 | 5 | uint64_t tt0 = now_ns(); |
6349 | 5 | LatValue thawed = value_thaw(&val); |
6350 | 5 | ev->stats.thaw_total_ns += now_ns() - tt0; |
6351 | 5 | value_free(&val); |
6352 | | |
6353 | | /* Call the transformation closure */ |
6354 | 5 | EvalResult tr = call_closure(ev, |
6355 | 5 | clr.value.as.closure.param_names, |
6356 | 5 | clr.value.as.closure.param_count, |
6357 | 5 | clr.value.as.closure.body, |
6358 | 5 | clr.value.as.closure.captured_env, |
6359 | 5 | &thawed, 1, |
6360 | 5 | clr.value.as.closure.default_values, |
6361 | 5 | clr.value.as.closure.has_variadic); |
6362 | 5 | value_free(&clr.value); |
6363 | | |
6364 | 5 | if (!IS_OK(tr)) { |
6365 | 1 | char *msg = NULL; |
6366 | 1 | (void)asprintf(&msg, "anneal failed: %s", tr.error); |
6367 | 1 | free(tr.error); |
6368 | 1 | return eval_err(msg); |
6369 | 1 | } |
6370 | | |
6371 | | /* Refreeze */ |
6372 | 4 | uint64_t ft0 = now_ns(); |
6373 | 4 | tr.value = value_freeze(tr.value); |
6374 | 4 | freeze_to_region(ev, &tr.value); |
6375 | 4 | ev->stats.freeze_total_ns += now_ns() - ft0; |
6376 | | |
6377 | | /* Update binding in-place */ |
6378 | 4 | LatValue ret = value_deep_clone(&tr.value); |
6379 | 4 | env_set(ev->env, name, tr.value); |
6380 | 4 | record_history(ev, name); |
6381 | 4 | { |
6382 | 4 | char *cascade_err = freeze_cascade(ev, name); |
6383 | 4 | if (cascade_err) { value_free(&ret); return eval_err(cascade_err); } |
6384 | 4 | } |
6385 | 4 | EvalResult fr = fire_reactions(ev, name, "crystal"); |
6386 | 4 | if (!IS_OK(fr)) { value_free(&ret); return fr; } |
6387 | 4 | return eval_ok(ret); |
6388 | 4 | } |
6389 | | |
6390 | | /* General expression path */ |
6391 | 1 | EvalResult er = eval_expr(ev, expr->as.anneal.expr); |
6392 | 1 | if (!IS_OK(er)) { value_free(&clr.value); return er; } |
6393 | | |
6394 | 1 | if (er.value.phase != VTAG_CRYSTAL) { |
6395 | 0 | value_free(&er.value); |
6396 | 0 | value_free(&clr.value); |
6397 | 0 | return eval_err(strdup("anneal requires a crystal value")); |
6398 | 0 | } |
6399 | | |
6400 | | /* Thaw */ |
6401 | 1 | uint64_t tt0 = now_ns(); |
6402 | 1 | LatValue thawed = value_thaw(&er.value); |
6403 | 1 | ev->stats.thaw_total_ns += now_ns() - tt0; |
6404 | 1 | value_free(&er.value); |
6405 | | |
6406 | | /* Call transformation */ |
6407 | 1 | EvalResult tr = call_closure(ev, |
6408 | 1 | clr.value.as.closure.param_names, |
6409 | 1 | clr.value.as.closure.param_count, |
6410 | 1 | clr.value.as.closure.body, |
6411 | 1 | clr.value.as.closure.captured_env, |
6412 | 1 | &thawed, 1, |
6413 | 1 | clr.value.as.closure.default_values, |
6414 | 1 | clr.value.as.closure.has_variadic); |
6415 | 1 | value_free(&clr.value); |
6416 | | |
6417 | 1 | if (!IS_OK(tr)) { |
6418 | 0 | char *msg = NULL; |
6419 | 0 | (void)asprintf(&msg, "anneal failed: %s", tr.error); |
6420 | 0 | free(tr.error); |
6421 | 0 | return eval_err(msg); |
6422 | 0 | } |
6423 | | |
6424 | | /* Refreeze */ |
6425 | 1 | uint64_t ft0 = now_ns(); |
6426 | 1 | tr.value = value_freeze(tr.value); |
6427 | 1 | freeze_to_region(ev, &tr.value); |
6428 | 1 | ev->stats.freeze_total_ns += now_ns() - ft0; |
6429 | 1 | return eval_ok(tr.value); |
6430 | 1 | } |
6431 | | |
6432 | 4 | case EXPR_CRYSTALLIZE: { |
6433 | | /* crystallize(expr) { body } — temporarily freeze, execute body, restore */ |
6434 | 4 | if (expr->as.crystallize.expr->tag != EXPR_IDENT) { |
6435 | 0 | return eval_err(strdup("crystallize() target must be a variable name")); |
6436 | 0 | } |
6437 | 4 | const char *name = expr->as.crystallize.expr->as.str_val; |
6438 | 4 | LatValue val; |
6439 | 4 | if (!env_get(ev->env, name, &val)) { |
6440 | 0 | char *err = NULL; |
6441 | 0 | (void)asprintf(&err, "crystallize(): undefined variable '%s'", name); |
6442 | 0 | return eval_err(err); |
6443 | 0 | } |
6444 | 4 | PhaseTag saved_phase = val.phase; |
6445 | | /* If already crystal, just run the body */ |
6446 | 4 | if (saved_phase != VTAG_CRYSTAL) { |
6447 | 3 | val = value_freeze(val); |
6448 | 3 | env_set(ev->env, name, val); |
6449 | 3 | } else { |
6450 | 1 | value_free(&val); |
6451 | 1 | } |
6452 | | /* Execute body */ |
6453 | 4 | stats_scope_push(&ev->stats); |
6454 | 4 | env_push_scope(ev->env); |
6455 | 4 | EvalResult result = eval_block_stmts(ev, expr->as.crystallize.body, expr->as.crystallize.body_count); |
6456 | 4 | env_pop_scope(ev->env); |
6457 | 4 | stats_scope_pop(&ev->stats); |
6458 | | /* Restore original phase */ |
6459 | 4 | if (saved_phase != VTAG_CRYSTAL) { |
6460 | 3 | LatValue cur; |
6461 | 3 | if (env_get(ev->env, name, &cur)) { |
6462 | 3 | LatValue thawed = value_thaw(&cur); |
6463 | 3 | value_free(&cur); |
6464 | 3 | thawed.phase = saved_phase; |
6465 | 3 | env_set(ev->env, name, thawed); |
6466 | 3 | } |
6467 | 3 | } |
6468 | 4 | if (!IS_OK(result)) return result; |
6469 | 4 | return eval_ok(result.value); |
6470 | 4 | } |
6471 | | |
6472 | 5 | case EXPR_SUBLIMATE: { |
6473 | | /* sublimate(expr) — shallow freeze: top-level locked, children mutable */ |
6474 | 5 | if (expr->as.freeze_expr->tag == EXPR_IDENT) { |
6475 | 5 | const char *name = expr->as.freeze_expr->as.str_val; |
6476 | 5 | LatValue val; |
6477 | 5 | if (!env_get(ev->env, name, &val)) { |
6478 | 0 | char *err = NULL; |
6479 | 0 | (void)asprintf(&err, "sublimate(): undefined variable '%s'", name); |
6480 | 0 | return eval_err(err); |
6481 | 0 | } |
6482 | 5 | val.phase = VTAG_SUBLIMATED; /* Only set top-level phase, don't recurse */ |
6483 | 5 | LatValue ret = value_deep_clone(&val); |
6484 | 5 | env_set(ev->env, name, val); |
6485 | 5 | record_history(ev, name); |
6486 | 5 | EvalResult fr = fire_reactions(ev, name, "sublimated"); |
6487 | 5 | if (!IS_OK(fr)) { value_free(&ret); return fr; } |
6488 | 5 | return eval_ok(ret); |
6489 | 5 | } |
6490 | 0 | EvalResult er = eval_expr(ev, expr->as.freeze_expr); |
6491 | 0 | if (!IS_OK(er)) return er; |
6492 | 0 | er.value.phase = VTAG_SUBLIMATED; |
6493 | 0 | return eval_ok(er.value); |
6494 | 0 | } |
6495 | | |
6496 | 1 | case EXPR_FORGE: { |
6497 | 1 | stats_forge(&ev->stats); |
6498 | 1 | stats_scope_push(&ev->stats); |
6499 | 1 | env_push_scope(ev->env); |
6500 | 1 | EvalResult result = eval_block_stmts(ev, expr->as.block.stmts, expr->as.block.count); |
6501 | 1 | env_pop_scope(ev->env); |
6502 | 1 | stats_scope_pop(&ev->stats); |
6503 | 1 | stats_freeze(&ev->stats); |
6504 | 1 | if (IS_OK(result)) { |
6505 | 1 | uint64_t ft0 = now_ns(); |
6506 | 1 | result.value = value_freeze(result.value); |
6507 | 1 | freeze_to_region(ev, &result.value); |
6508 | 1 | ev->stats.freeze_total_ns += now_ns() - ft0; |
6509 | 1 | return eval_ok(result.value); |
6510 | 1 | } |
6511 | 0 | if (IS_SIGNAL(result) && result.cf.tag == CF_RETURN) { |
6512 | 0 | uint64_t ft0 = now_ns(); |
6513 | 0 | result.cf.value = value_freeze(result.cf.value); |
6514 | 0 | freeze_to_region(ev, &result.cf.value); |
6515 | 0 | ev->stats.freeze_total_ns += now_ns() - ft0; |
6516 | 0 | return eval_ok(result.cf.value); |
6517 | 0 | } |
6518 | 0 | return result; |
6519 | 0 | } |
6520 | | |
6521 | 1.33k | case EXPR_IF: { |
6522 | 1.33k | EvalResult condr = eval_expr(ev, expr->as.if_expr.cond); |
6523 | 1.33k | if (!IS_OK(condr)) return condr; |
6524 | 1.33k | bool truthy = value_is_truthy(&condr.value); |
6525 | 1.33k | value_free(&condr.value); |
6526 | 1.33k | if (truthy) { |
6527 | 362 | stats_scope_push(&ev->stats); |
6528 | 362 | env_push_scope(ev->env); |
6529 | 362 | EvalResult r = eval_block_stmts(ev, expr->as.if_expr.then_stmts, |
6530 | 362 | expr->as.if_expr.then_count); |
6531 | 362 | env_pop_scope(ev->env); |
6532 | 362 | stats_scope_pop(&ev->stats); |
6533 | 362 | return r; |
6534 | 969 | } else if (expr->as.if_expr.else_stmts) { |
6535 | 194 | stats_scope_push(&ev->stats); |
6536 | 194 | env_push_scope(ev->env); |
6537 | 194 | EvalResult r = eval_block_stmts(ev, expr->as.if_expr.else_stmts, |
6538 | 194 | expr->as.if_expr.else_count); |
6539 | 194 | env_pop_scope(ev->env); |
6540 | 194 | stats_scope_pop(&ev->stats); |
6541 | 194 | return r; |
6542 | 194 | } |
6543 | 775 | return eval_ok(value_unit()); |
6544 | 1.33k | } |
6545 | | |
6546 | 645 | case EXPR_BLOCK: { |
6547 | 645 | stats_scope_push(&ev->stats); |
6548 | 645 | env_push_scope(ev->env); |
6549 | 645 | EvalResult r = eval_block_stmts(ev, expr->as.block.stmts, expr->as.block.count); |
6550 | 645 | env_pop_scope(ev->env); |
6551 | 645 | stats_scope_pop(&ev->stats); |
6552 | 645 | return r; |
6553 | 1.33k | } |
6554 | | |
6555 | 148 | case EXPR_CLOSURE: { |
6556 | 148 | stats_closure(&ev->stats); |
6557 | 148 | gc_maybe_collect(ev); |
6558 | 148 | Env *captured = env_clone(ev->env); |
6559 | 148 | return eval_ok(value_closure( |
6560 | 148 | expr->as.closure.params, |
6561 | 148 | expr->as.closure.param_count, |
6562 | 148 | expr->as.closure.body, |
6563 | 148 | captured, |
6564 | 148 | expr->as.closure.default_values, |
6565 | 148 | expr->as.closure.has_variadic)); |
6566 | 1.33k | } |
6567 | | |
6568 | 22 | case EXPR_RANGE: { |
6569 | 22 | EvalResult sr = eval_expr(ev, expr->as.range.start); |
6570 | 22 | if (!IS_OK(sr)) return sr; |
6571 | 22 | GC_PUSH(ev, &sr.value); |
6572 | 22 | EvalResult er = eval_expr(ev, expr->as.range.end); |
6573 | 22 | GC_POP(ev); |
6574 | 22 | if (!IS_OK(er)) { value_free(&sr.value); return er; } |
6575 | 22 | if (sr.value.type != VAL_INT || er.value.type != VAL_INT) { |
6576 | 0 | value_free(&sr.value); value_free(&er.value); |
6577 | 0 | return eval_err(strdup("range bounds must be integers")); |
6578 | 0 | } |
6579 | 22 | int64_t s = sr.value.as.int_val, e = er.value.as.int_val; |
6580 | 22 | return eval_ok(value_range(s, e)); |
6581 | 22 | } |
6582 | | |
6583 | | /// @builtin print(args: Any...) -> Unit |
6584 | | /// @category Core |
6585 | | /// Print values separated by spaces with a trailing newline. |
6586 | | /// @example print("hello", "world") // prints: hello world |
6587 | 1.01k | case EXPR_PRINT: { |
6588 | 2.03k | for (size_t i = 0; i < expr->as.print.arg_count; i++) { |
6589 | 1.02k | if (i > 0) printf(" "); |
6590 | 1.02k | EvalResult er = eval_expr(ev, expr->as.print.args[i]); |
6591 | 1.02k | if (!IS_OK(er)) return er; |
6592 | 1.01k | char *s = value_display(&er.value); |
6593 | 1.01k | printf("%s", s); |
6594 | 1.01k | free(s); |
6595 | 1.01k | value_free(&er.value); |
6596 | 1.01k | } |
6597 | 1.01k | printf("\n"); |
6598 | 1.01k | return eval_ok(value_unit()); |
6599 | 1.01k | } |
6600 | | |
6601 | 26 | case EXPR_TRY_CATCH: { |
6602 | 26 | stats_scope_push(&ev->stats); |
6603 | 26 | env_push_scope(ev->env); |
6604 | 26 | EvalResult tr = eval_block_stmts(ev, expr->as.try_catch.try_stmts, |
6605 | 26 | expr->as.try_catch.try_count); |
6606 | 26 | env_pop_scope(ev->env); |
6607 | 26 | stats_scope_pop(&ev->stats); |
6608 | 26 | if (IS_ERR(tr)) { |
6609 | | /* Error: bind to catch variable and execute catch block */ |
6610 | 21 | stats_scope_push(&ev->stats); |
6611 | 21 | env_push_scope(ev->env); |
6612 | 21 | env_define(ev->env, expr->as.try_catch.catch_var, value_string(tr.error)); |
6613 | 21 | free(tr.error); |
6614 | 21 | EvalResult cr = eval_block_stmts(ev, expr->as.try_catch.catch_stmts, |
6615 | 21 | expr->as.try_catch.catch_count); |
6616 | 21 | env_pop_scope(ev->env); |
6617 | 21 | stats_scope_pop(&ev->stats); |
6618 | 21 | return cr; |
6619 | 21 | } |
6620 | 5 | return tr; |
6621 | 26 | } |
6622 | | |
6623 | 1 | case EXPR_SPAWN: { |
6624 | 1 | stats_scope_push(&ev->stats); |
6625 | 1 | env_push_scope(ev->env); |
6626 | 1 | EvalResult result = eval_block_stmts(ev, expr->as.block.stmts, expr->as.block.count); |
6627 | 1 | env_pop_scope(ev->env); |
6628 | 1 | stats_scope_pop(&ev->stats); |
6629 | 1 | if (IS_SIGNAL(result) && result.cf.tag == CF_RETURN) { |
6630 | 1 | return eval_ok(result.cf.value); |
6631 | 1 | } |
6632 | 0 | return result; |
6633 | 1 | } |
6634 | | |
6635 | 3 | case EXPR_SCOPE: { |
6636 | | #ifdef __EMSCRIPTEN__ |
6637 | | /* WASM fallback: run as a regular block */ |
6638 | | stats_scope_push(&ev->stats); |
6639 | | env_push_scope(ev->env); |
6640 | | EvalResult result = eval_block_stmts(ev, expr->as.block.stmts, expr->as.block.count); |
6641 | | env_pop_scope(ev->env); |
6642 | | stats_scope_pop(&ev->stats); |
6643 | | return result; |
6644 | | #else |
6645 | | /* Count spawn statements */ |
6646 | 3 | size_t total = expr->as.block.count; |
6647 | 3 | size_t spawn_count = 0; |
6648 | 9 | for (size_t i = 0; i < total; i++) { |
6649 | 6 | Stmt *s = expr->as.block.stmts[i]; |
6650 | 6 | if (s->tag == STMT_EXPR && s->as.expr->tag == EXPR_SPAWN) |
6651 | 3 | spawn_count++; |
6652 | 6 | } |
6653 | | |
6654 | 3 | if (spawn_count == 0) { |
6655 | | /* No spawns — run as regular block */ |
6656 | 1 | stats_scope_push(&ev->stats); |
6657 | 1 | env_push_scope(ev->env); |
6658 | 1 | EvalResult result = eval_block_stmts(ev, expr->as.block.stmts, total); |
6659 | 1 | env_pop_scope(ev->env); |
6660 | 1 | stats_scope_pop(&ev->stats); |
6661 | 1 | return result; |
6662 | 1 | } |
6663 | | |
6664 | | /* Run non-spawn statements synchronously, spawn tasks in parallel */ |
6665 | 2 | SpawnTask *tasks = calloc(spawn_count, sizeof(SpawnTask)); |
6666 | 2 | size_t task_idx = 0; |
6667 | 2 | char *first_error = NULL; |
6668 | | |
6669 | 2 | stats_scope_push(&ev->stats); |
6670 | 2 | env_push_scope(ev->env); |
6671 | | |
6672 | | /* Execute non-spawn statements first, create tasks for spawns */ |
6673 | 5 | for (size_t i = 0; i < total; i++) { |
6674 | 3 | Stmt *s = expr->as.block.stmts[i]; |
6675 | 3 | if (s->tag == STMT_EXPR && s->as.expr->tag == EXPR_SPAWN) { |
6676 | 3 | Expr *spawn_expr = s->as.expr; |
6677 | 3 | tasks[task_idx].stmts = spawn_expr->as.block.stmts; |
6678 | 3 | tasks[task_idx].stmt_count = spawn_expr->as.block.count; |
6679 | 3 | tasks[task_idx].child_ev = create_child_evaluator(ev); |
6680 | 3 | tasks[task_idx].error = NULL; |
6681 | 3 | task_idx++; |
6682 | 3 | } else { |
6683 | 0 | if (!first_error) { |
6684 | 0 | EvalResult r = eval_stmt(ev, s); |
6685 | 0 | if (IS_ERR(r)) { |
6686 | 0 | first_error = r.error; |
6687 | 0 | } else if (IS_SIGNAL(r)) { |
6688 | 0 | first_error = strdup("unexpected control flow in scope"); |
6689 | 0 | value_free(&r.cf.value); |
6690 | 0 | } else { |
6691 | 0 | value_free(&r.value); |
6692 | 0 | } |
6693 | 0 | } |
6694 | 0 | } |
6695 | 3 | } |
6696 | | |
6697 | | /* Launch all spawn threads */ |
6698 | 5 | for (size_t i = 0; i < task_idx; i++) { |
6699 | 3 | pthread_create(&tasks[i].thread, NULL, spawn_thread_fn, &tasks[i]); |
6700 | 3 | } |
6701 | | |
6702 | | /* Join all threads */ |
6703 | 5 | for (size_t i = 0; i < task_idx; i++) { |
6704 | 3 | pthread_join(tasks[i].thread, NULL); |
6705 | 3 | } |
6706 | | |
6707 | | /* Restore parent TLS heap */ |
6708 | 2 | value_set_heap(ev->heap); |
6709 | 2 | value_set_arena(NULL); |
6710 | | |
6711 | | /* Collect first error from child threads */ |
6712 | 5 | for (size_t i = 0; i < task_idx; i++) { |
6713 | 3 | if (tasks[i].error && !first_error) { |
6714 | 1 | first_error = tasks[i].error; |
6715 | 2 | } else if (tasks[i].error) { |
6716 | 0 | free(tasks[i].error); |
6717 | 0 | } |
6718 | 3 | free_child_evaluator(tasks[i].child_ev); |
6719 | 3 | } |
6720 | 2 | free(tasks); |
6721 | | |
6722 | 2 | env_pop_scope(ev->env); |
6723 | 2 | stats_scope_pop(&ev->stats); |
6724 | | |
6725 | 2 | if (first_error) return eval_err(first_error); |
6726 | 1 | return eval_ok(value_unit()); |
6727 | 2 | #endif |
6728 | 2 | } |
6729 | | |
6730 | 18 | case EXPR_INTERP_STRING: { |
6731 | 18 | size_t count = expr->as.interp.count; |
6732 | | /* Estimate buffer size */ |
6733 | 18 | size_t buf_cap = 64; |
6734 | 18 | size_t buf_len = 0; |
6735 | 18 | char *buf = malloc(buf_cap); |
6736 | 48 | for (size_t i = 0; i < count; i++) { |
6737 | | /* Append string segment */ |
6738 | 30 | const char *part = expr->as.interp.parts[i]; |
6739 | 30 | size_t plen = strlen(part); |
6740 | 30 | while (buf_len + plen + 1 >= buf_cap) { buf_cap *= 2; buf = realloc(buf, buf_cap); } |
6741 | 30 | memcpy(buf + buf_len, part, plen); |
6742 | 30 | buf_len += plen; |
6743 | | /* Evaluate expression and convert to string */ |
6744 | 30 | EvalResult er = eval_expr(ev, expr->as.interp.exprs[i]); |
6745 | 30 | if (!IS_OK(er)) { free(buf); return er; } |
6746 | 30 | char *s = value_display(&er.value); |
6747 | 30 | value_free(&er.value); |
6748 | 30 | size_t slen = strlen(s); |
6749 | 30 | while (buf_len + slen + 1 >= buf_cap) { buf_cap *= 2; buf = realloc(buf, buf_cap); } |
6750 | 30 | memcpy(buf + buf_len, s, slen); |
6751 | 30 | buf_len += slen; |
6752 | 30 | free(s); |
6753 | 30 | } |
6754 | | /* Append trailing segment */ |
6755 | 18 | const char *last_part = expr->as.interp.parts[count]; |
6756 | 18 | size_t lplen = strlen(last_part); |
6757 | 18 | while (buf_len + lplen + 1 >= buf_cap) { buf_cap *= 2; buf = realloc(buf, buf_cap); } |
6758 | 18 | memcpy(buf + buf_len, last_part, lplen); |
6759 | 18 | buf_len += lplen; |
6760 | 18 | buf[buf_len] = '\0'; |
6761 | 18 | return eval_ok(value_string_owned(buf)); |
6762 | 18 | } |
6763 | 15 | case EXPR_MATCH: { |
6764 | 15 | EvalResult scr = eval_expr(ev, expr->as.match_expr.scrutinee); |
6765 | 15 | if (!IS_OK(scr)) return scr; |
6766 | 15 | GC_PUSH(ev, &scr.value); |
6767 | | |
6768 | 22 | for (size_t i = 0; i < expr->as.match_expr.arm_count; i++) { |
6769 | 22 | MatchArm *arm = &expr->as.match_expr.arms[i]; |
6770 | 22 | bool matched = false; |
6771 | 22 | char *bind_name = NULL; |
6772 | 22 | LatValue bind_val = value_unit(); |
6773 | | |
6774 | 22 | switch (arm->pattern->tag) { |
6775 | 7 | case PAT_WILDCARD: |
6776 | 7 | matched = true; |
6777 | 7 | break; |
6778 | 5 | case PAT_BINDING: |
6779 | 5 | matched = true; |
6780 | 5 | bind_name = arm->pattern->as.binding_name; |
6781 | 5 | bind_val = value_deep_clone(&scr.value); |
6782 | 5 | break; |
6783 | 8 | case PAT_LITERAL: { |
6784 | 8 | EvalResult pr = eval_expr(ev, arm->pattern->as.literal); |
6785 | 8 | if (!IS_OK(pr)) { GC_POP(ev); value_free(&scr.value); return pr; } |
6786 | 8 | matched = value_equal(&scr.value, &pr.value); |
6787 | 8 | value_free(&pr.value); |
6788 | 8 | break; |
6789 | 8 | } |
6790 | 2 | case PAT_RANGE: { |
6791 | 2 | EvalResult sr = eval_expr(ev, arm->pattern->as.range.start); |
6792 | 2 | if (!IS_OK(sr)) { GC_POP(ev); value_free(&scr.value); return sr; } |
6793 | 2 | EvalResult er = eval_expr(ev, arm->pattern->as.range.end); |
6794 | 2 | if (!IS_OK(er)) { GC_POP(ev); value_free(&scr.value); value_free(&sr.value); return er; } |
6795 | 2 | if (scr.value.type == VAL_INT && sr.value.type == VAL_INT && er.value.type == VAL_INT) { |
6796 | 2 | matched = scr.value.as.int_val >= sr.value.as.int_val && |
6797 | 2 | scr.value.as.int_val <= er.value.as.int_val; |
6798 | 2 | } |
6799 | 2 | value_free(&sr.value); |
6800 | 2 | value_free(&er.value); |
6801 | 2 | break; |
6802 | 2 | } |
6803 | 22 | } |
6804 | | |
6805 | | /* Check phase qualifier */ |
6806 | 22 | if (matched && arm->pattern->phase_qualifier != PHASE_UNSPECIFIED) { |
6807 | 7 | bool phase_ok = false; |
6808 | 7 | if (arm->pattern->phase_qualifier == PHASE_FLUID) |
6809 | 3 | phase_ok = (scr.value.phase == VTAG_FLUID || scr.value.phase == VTAG_UNPHASED); |
6810 | 4 | else if (arm->pattern->phase_qualifier == PHASE_CRYSTAL) |
6811 | 4 | phase_ok = (scr.value.phase == VTAG_CRYSTAL); |
6812 | 7 | if (!phase_ok) { |
6813 | 3 | if (bind_name) { value_free(&bind_val); bind_name = NULL; } |
6814 | 3 | continue; |
6815 | 3 | } |
6816 | 7 | } |
6817 | | |
6818 | 19 | if (!matched) continue; |
6819 | | |
6820 | | /* Check guard */ |
6821 | 15 | if (arm->guard) { |
6822 | 2 | env_push_scope(ev->env); |
6823 | 2 | if (bind_name) env_define(ev->env, bind_name, value_deep_clone(&bind_val)); |
6824 | 2 | EvalResult gr = eval_expr(ev, arm->guard); |
6825 | 2 | env_pop_scope(ev->env); |
6826 | 2 | if (!IS_OK(gr)) { |
6827 | 0 | if (bind_name) value_free(&bind_val); |
6828 | 0 | GC_POP(ev); value_free(&scr.value); |
6829 | 0 | return gr; |
6830 | 0 | } |
6831 | 2 | bool guard_pass = gr.value.type == VAL_BOOL && gr.value.as.bool_val; |
6832 | 2 | value_free(&gr.value); |
6833 | 2 | if (!guard_pass) { |
6834 | 0 | if (bind_name) value_free(&bind_val); |
6835 | 0 | continue; |
6836 | 0 | } |
6837 | 2 | } |
6838 | | |
6839 | | /* Execute arm body */ |
6840 | 15 | env_push_scope(ev->env); |
6841 | 15 | if (bind_name) env_define(ev->env, bind_name, bind_val); |
6842 | 15 | EvalResult result = eval_ok(value_unit()); |
6843 | 31 | for (size_t j = 0; j < arm->body_count; j++) { |
6844 | 16 | value_free(&result.value); |
6845 | 16 | result = eval_stmt(ev, arm->body[j]); |
6846 | 16 | if (!IS_OK(result)) break; |
6847 | 16 | } |
6848 | 15 | env_pop_scope(ev->env); |
6849 | 15 | GC_POP(ev); value_free(&scr.value); |
6850 | 15 | return result; |
6851 | 15 | } |
6852 | | |
6853 | 0 | GC_POP(ev); value_free(&scr.value); |
6854 | 0 | return eval_ok(value_nil()); |
6855 | 15 | } |
6856 | | |
6857 | 488 | case EXPR_ENUM_VARIANT: { |
6858 | 488 | const char *enum_name = expr->as.enum_variant.enum_name; |
6859 | 488 | const char *variant_name = expr->as.enum_variant.variant_name; |
6860 | | |
6861 | 488 | EnumDecl *ed = find_enum(ev, enum_name); |
6862 | 488 | if (!ed) { |
6863 | | /* Fall back to static call: Name::method(args) |
6864 | | * Build a temporary EXPR_CALL node and evaluate it so that |
6865 | | * builtins like Map::new(), Channel::new() etc. work. */ |
6866 | 474 | size_t ac = expr->as.enum_variant.arg_count; |
6867 | 474 | size_t nlen = strlen(enum_name) + 2 + strlen(variant_name) + 1; |
6868 | 474 | char *full = malloc(nlen); |
6869 | 474 | snprintf(full, nlen, "%s::%s", enum_name, variant_name); |
6870 | | |
6871 | | /* Borrow the arg expressions for the temp node */ |
6872 | 474 | Expr **arg_refs = NULL; |
6873 | 474 | if (ac > 0) { |
6874 | 45 | arg_refs = malloc(ac * sizeof(Expr *)); |
6875 | 90 | for (size_t i = 0; i < ac; i++) |
6876 | 45 | arg_refs[i] = expr->as.enum_variant.args[i]; |
6877 | 45 | } |
6878 | | |
6879 | | /* Build a temporary EXPR_CALL: full(args...) |
6880 | | * Use a stack-allocated EXPR_IDENT for the func. */ |
6881 | 474 | Expr tmp_ident; |
6882 | 474 | tmp_ident.tag = EXPR_IDENT; |
6883 | 474 | tmp_ident.as.str_val = full; |
6884 | | |
6885 | 474 | Expr tmp_call; |
6886 | 474 | tmp_call.tag = EXPR_CALL; |
6887 | 474 | tmp_call.as.call.func = &tmp_ident; |
6888 | 474 | tmp_call.as.call.args = arg_refs; |
6889 | 474 | tmp_call.as.call.arg_count = ac; |
6890 | | |
6891 | 474 | EvalResult cr = eval_expr(ev, &tmp_call); |
6892 | | |
6893 | | /* Don't free arg expressions — they belong to the original node */ |
6894 | 474 | free(arg_refs); |
6895 | 474 | free(full); |
6896 | 474 | return cr; |
6897 | 474 | } |
6898 | | |
6899 | 14 | VariantDecl *vd = find_variant(ed, variant_name); |
6900 | 14 | if (!vd) { |
6901 | 0 | char *err2 = NULL; |
6902 | 0 | (void)asprintf(&err2, "enum '%s' has no variant '%s'", enum_name, variant_name); |
6903 | 0 | return eval_err(err2); |
6904 | 0 | } |
6905 | | |
6906 | 14 | size_t provided = expr->as.enum_variant.arg_count; |
6907 | 14 | if (provided != vd->param_count) { |
6908 | 0 | char *err2 = NULL; |
6909 | 0 | (void)asprintf(&err2, "variant '%s::%s' expects %zu argument%s, got %zu", |
6910 | 0 | enum_name, variant_name, vd->param_count, |
6911 | 0 | vd->param_count == 1 ? "" : "s", provided); |
6912 | 0 | return eval_err(err2); |
6913 | 0 | } |
6914 | | |
6915 | 14 | LatValue *payload = NULL; |
6916 | 14 | if (provided > 0) { |
6917 | 4 | payload = malloc(provided * sizeof(LatValue)); |
6918 | 10 | for (size_t i = 0; i < provided; i++) { |
6919 | 6 | EvalResult er = eval_expr(ev, expr->as.enum_variant.args[i]); |
6920 | 6 | if (!IS_OK(er)) { |
6921 | 0 | for (size_t j = 0; j < i; j++) value_free(&payload[j]); |
6922 | 0 | free(payload); |
6923 | 0 | return er; |
6924 | 0 | } |
6925 | 6 | payload[i] = er.value; |
6926 | 6 | } |
6927 | 4 | } |
6928 | 14 | LatValue enm = value_enum(enum_name, variant_name, payload, provided); |
6929 | 14 | if (payload) { |
6930 | 10 | for (size_t i = 0; i < provided; i++) value_free(&payload[i]); |
6931 | 4 | free(payload); |
6932 | 4 | } |
6933 | 14 | return eval_ok(enm); |
6934 | 14 | } |
6935 | | |
6936 | 6 | case EXPR_TRY_PROPAGATE: { |
6937 | 6 | EvalResult inner = eval_expr(ev, expr->as.try_propagate_expr); |
6938 | 6 | if (!inner.ok) return inner; |
6939 | 6 | if (inner.value.type != VAL_MAP) { |
6940 | 1 | value_free(&inner.value); |
6941 | 1 | return eval_err(strdup("? operator requires a Result map (got non-Map value)")); |
6942 | 1 | } |
6943 | 5 | LatValue *tag = lat_map_get(inner.value.as.map.map, "tag"); |
6944 | 5 | if (!tag || tag->type != VAL_STR) { |
6945 | 0 | value_free(&inner.value); |
6946 | 0 | return eval_err(strdup("? operator requires a Map with a string \"tag\" field")); |
6947 | 0 | } |
6948 | 5 | if (strcmp(tag->as.str_val, "ok") == 0) { |
6949 | 3 | LatValue *val = lat_map_get(inner.value.as.map.map, "value"); |
6950 | 3 | LatValue result = val ? value_deep_clone(val) : value_nil(); |
6951 | 3 | value_free(&inner.value); |
6952 | 3 | return eval_ok(result); |
6953 | 3 | } |
6954 | 2 | if (strcmp(tag->as.str_val, "err") == 0) { |
6955 | | /* Propagate: return from enclosing function with the err Map */ |
6956 | 2 | return eval_signal(CF_RETURN, inner.value); |
6957 | 2 | } |
6958 | 0 | value_free(&inner.value); |
6959 | 0 | return eval_err(strdup("? operator: tag must be \"ok\" or \"err\"")); |
6960 | 2 | } |
6961 | | |
6962 | 5 | case EXPR_SELECT: { |
6963 | | #ifdef __EMSCRIPTEN__ |
6964 | | return eval_err(strdup("select is not supported in WASM builds")); |
6965 | | #else |
6966 | 5 | size_t arm_count = expr->as.select_expr.arm_count; |
6967 | 5 | SelectArm *arms = expr->as.select_expr.arms; |
6968 | | |
6969 | | /* Find default and timeout arms */ |
6970 | 5 | int default_idx = -1; |
6971 | 5 | int timeout_idx = -1; |
6972 | 14 | for (size_t i = 0; i < arm_count; i++) { |
6973 | 9 | if (arms[i].is_default) default_idx = (int)i; |
6974 | 9 | if (arms[i].is_timeout) timeout_idx = (int)i; |
6975 | 9 | } |
6976 | | |
6977 | | /* Evaluate all channel expressions upfront */ |
6978 | 5 | LatChannel **channels = calloc(arm_count, sizeof(LatChannel *)); |
6979 | 14 | for (size_t i = 0; i < arm_count; i++) { |
6980 | 9 | if (arms[i].is_default || arms[i].is_timeout) continue; |
6981 | 6 | EvalResult cer = eval_expr(ev, arms[i].channel_expr); |
6982 | 6 | if (!IS_OK(cer)) { free(channels); return cer; } |
6983 | 6 | if (cer.value.type != VAL_CHANNEL) { |
6984 | 0 | value_free(&cer.value); |
6985 | 0 | free(channels); |
6986 | 0 | return eval_err(strdup("select arm: expression is not a Channel")); |
6987 | 0 | } |
6988 | 6 | channels[i] = cer.value.as.channel.ch; |
6989 | 6 | channel_retain(channels[i]); |
6990 | 6 | value_free(&cer.value); |
6991 | 6 | } |
6992 | | |
6993 | | /* Evaluate timeout if present */ |
6994 | 5 | long timeout_ms = -1; |
6995 | 5 | if (timeout_idx >= 0) { |
6996 | 0 | EvalResult ter = eval_expr(ev, arms[timeout_idx].timeout_expr); |
6997 | 0 | if (!IS_OK(ter)) { |
6998 | 0 | for (size_t i = 0; i < arm_count; i++) |
6999 | 0 | if (channels[i]) channel_release(channels[i]); |
7000 | 0 | free(channels); |
7001 | 0 | return ter; |
7002 | 0 | } |
7003 | 0 | if (ter.value.type != VAL_INT) { |
7004 | 0 | value_free(&ter.value); |
7005 | 0 | for (size_t i = 0; i < arm_count; i++) |
7006 | 0 | if (channels[i]) channel_release(channels[i]); |
7007 | 0 | free(channels); |
7008 | 0 | return eval_err(strdup("select timeout must be an integer (milliseconds)")); |
7009 | 0 | } |
7010 | 0 | timeout_ms = (long)ter.value.as.int_val; |
7011 | 0 | value_free(&ter.value); |
7012 | 0 | } |
7013 | | |
7014 | | /* Build shuffled index array for fairness */ |
7015 | 5 | size_t ch_arm_count = 0; |
7016 | 5 | size_t *indices = malloc(arm_count * sizeof(size_t)); |
7017 | 14 | for (size_t i = 0; i < arm_count; i++) { |
7018 | 9 | if (!arms[i].is_default && !arms[i].is_timeout) |
7019 | 6 | indices[ch_arm_count++] = i; |
7020 | 9 | } |
7021 | | /* Fisher-Yates shuffle */ |
7022 | 6 | for (size_t i = ch_arm_count; i > 1; i--) { |
7023 | 1 | size_t j = (size_t)rand() % i; |
7024 | 1 | size_t tmp = indices[i-1]; |
7025 | 1 | indices[i-1] = indices[j]; |
7026 | 1 | indices[j] = tmp; |
7027 | 1 | } |
7028 | | |
7029 | | /* Set up waiter for blocking */ |
7030 | 5 | pthread_mutex_t sel_mutex = PTHREAD_MUTEX_INITIALIZER; |
7031 | 5 | pthread_cond_t sel_cond = PTHREAD_COND_INITIALIZER; |
7032 | 5 | LatSelectWaiter waiter = { |
7033 | 5 | .mutex = &sel_mutex, |
7034 | 5 | .cond = &sel_cond, |
7035 | 5 | .next = NULL, |
7036 | 5 | }; |
7037 | | |
7038 | 5 | EvalResult select_result = eval_ok(value_unit()); |
7039 | 5 | bool found = false; |
7040 | | |
7041 | | /* Compute deadline for timeout */ |
7042 | 5 | struct timespec deadline; |
7043 | 5 | if (timeout_ms >= 0) { |
7044 | 0 | clock_gettime(CLOCK_REALTIME, &deadline); |
7045 | 0 | deadline.tv_sec += timeout_ms / 1000; |
7046 | 0 | deadline.tv_nsec += (timeout_ms % 1000) * 1000000L; |
7047 | 0 | if (deadline.tv_nsec >= 1000000000L) { |
7048 | 0 | deadline.tv_sec++; |
7049 | 0 | deadline.tv_nsec -= 1000000000L; |
7050 | 0 | } |
7051 | 0 | } |
7052 | | |
7053 | 5 | for (;;) { |
7054 | | /* Try non-blocking recv on each channel arm (shuffled order) */ |
7055 | 5 | bool all_closed = true; |
7056 | 9 | for (size_t k = 0; k < ch_arm_count; k++) { |
7057 | 6 | size_t i = indices[k]; |
7058 | 6 | LatChannel *ch = channels[i]; |
7059 | 6 | LatValue recv_val; |
7060 | 6 | bool closed = false; |
7061 | 6 | if (channel_try_recv(ch, &recv_val, &closed)) { |
7062 | | /* Got a value — bind and execute body */ |
7063 | 2 | env_push_scope(ev->env); |
7064 | 2 | if (arms[i].binding_name) |
7065 | 2 | env_define(ev->env, arms[i].binding_name, recv_val); |
7066 | 0 | else |
7067 | 0 | value_free(&recv_val); |
7068 | 2 | select_result = eval_block_stmts(ev, arms[i].body, arms[i].body_count); |
7069 | 2 | env_pop_scope(ev->env); |
7070 | 2 | found = true; |
7071 | 2 | break; |
7072 | 2 | } |
7073 | 4 | if (!closed) all_closed = false; |
7074 | 4 | } |
7075 | 5 | if (found) break; |
7076 | | |
7077 | 3 | if (all_closed && ch_arm_count > 0) { |
7078 | | /* All channels closed — execute default if present, otherwise return unit */ |
7079 | 2 | if (default_idx >= 0) { |
7080 | 1 | env_push_scope(ev->env); |
7081 | 1 | select_result = eval_block_stmts(ev, arms[default_idx].body, arms[default_idx].body_count); |
7082 | 1 | env_pop_scope(ev->env); |
7083 | 1 | } |
7084 | 2 | break; |
7085 | 2 | } |
7086 | | |
7087 | | /* If there's a default arm, execute it immediately (non-blocking select) */ |
7088 | 1 | if (default_idx >= 0) { |
7089 | 1 | env_push_scope(ev->env); |
7090 | 1 | select_result = eval_block_stmts(ev, arms[default_idx].body, arms[default_idx].body_count); |
7091 | 1 | env_pop_scope(ev->env); |
7092 | 1 | break; |
7093 | 1 | } |
7094 | | |
7095 | | /* Block: register waiter on all channels, then wait */ |
7096 | 0 | for (size_t k = 0; k < ch_arm_count; k++) |
7097 | 0 | channel_add_waiter(channels[indices[k]], &waiter); |
7098 | |
|
7099 | 0 | pthread_mutex_lock(&sel_mutex); |
7100 | 0 | if (timeout_ms >= 0) { |
7101 | 0 | int rc = pthread_cond_timedwait(&sel_cond, &sel_mutex, &deadline); |
7102 | 0 | if (rc != 0) { |
7103 | | /* Timeout expired */ |
7104 | 0 | pthread_mutex_unlock(&sel_mutex); |
7105 | 0 | for (size_t k = 0; k < ch_arm_count; k++) |
7106 | 0 | channel_remove_waiter(channels[indices[k]], &waiter); |
7107 | 0 | if (timeout_idx >= 0) { |
7108 | 0 | env_push_scope(ev->env); |
7109 | 0 | select_result = eval_block_stmts(ev, arms[timeout_idx].body, arms[timeout_idx].body_count); |
7110 | 0 | env_pop_scope(ev->env); |
7111 | 0 | } |
7112 | 0 | break; |
7113 | 0 | } |
7114 | 0 | } else { |
7115 | 0 | pthread_cond_wait(&sel_cond, &sel_mutex); |
7116 | 0 | } |
7117 | 0 | pthread_mutex_unlock(&sel_mutex); |
7118 | | |
7119 | | /* Remove waiters and retry */ |
7120 | 0 | for (size_t k = 0; k < ch_arm_count; k++) |
7121 | 0 | channel_remove_waiter(channels[indices[k]], &waiter); |
7122 | 0 | } |
7123 | | |
7124 | 5 | pthread_mutex_destroy(&sel_mutex); |
7125 | 5 | pthread_cond_destroy(&sel_cond); |
7126 | 5 | free(indices); |
7127 | 14 | for (size_t i = 0; i < arm_count; i++) |
7128 | 9 | if (channels[i]) channel_release(channels[i]); |
7129 | 5 | free(channels); |
7130 | 5 | return select_result; |
7131 | 5 | #endif |
7132 | 5 | } |
7133 | | |
7134 | 0 | case EXPR_SPREAD: |
7135 | 0 | return eval_err(strdup("spread operator ... can only be used inside array literals")); |
7136 | 68.3k | } |
7137 | 0 | return eval_err(strdup("unknown expression type")); |
7138 | 68.3k | } |
7139 | | |
7140 | | /* ── Module loading ── */ |
7141 | | |
7142 | 94 | static EvalResult load_module(Evaluator *ev, const char *raw_path) { |
7143 | | /* Check for built-in stdlib module */ |
7144 | 94 | LatValue builtin_mod; |
7145 | 94 | if (rt_try_builtin_import(raw_path, &builtin_mod)) { |
7146 | 11 | return eval_ok(builtin_mod); |
7147 | 11 | } |
7148 | | |
7149 | | /* Resolve file path: append .lat if not present */ |
7150 | 83 | size_t plen = strlen(raw_path); |
7151 | 83 | char *file_path; |
7152 | 83 | if (plen >= 4 && strcmp(raw_path + plen - 4, ".lat") == 0) { |
7153 | 0 | file_path = strdup(raw_path); |
7154 | 83 | } else { |
7155 | 83 | file_path = malloc(plen + 5); |
7156 | 83 | memcpy(file_path, raw_path, plen); |
7157 | 83 | memcpy(file_path + plen, ".lat", 5); |
7158 | 83 | } |
7159 | | |
7160 | | /* Resolve to an absolute path */ |
7161 | 83 | char resolved[PATH_MAX]; |
7162 | 83 | bool found = (realpath(file_path, resolved) != NULL); |
7163 | 83 | if (!found && ev->script_dir && file_path[0] != '/') { |
7164 | 0 | char script_rel[PATH_MAX]; |
7165 | 0 | snprintf(script_rel, sizeof(script_rel), "%s/%s", ev->script_dir, file_path); |
7166 | 0 | found = (realpath(script_rel, resolved) != NULL); |
7167 | 0 | } |
7168 | 83 | if (!found) { |
7169 | 1 | char errbuf[512]; |
7170 | 1 | snprintf(errbuf, sizeof(errbuf), "import: cannot find '%s'", file_path); |
7171 | 1 | free(file_path); |
7172 | 1 | return eval_err(strdup(errbuf)); |
7173 | 1 | } |
7174 | 82 | free(file_path); |
7175 | | |
7176 | | /* Check module cache */ |
7177 | 82 | LatValue *cached = (LatValue *)lat_map_get(&ev->module_cache, resolved); |
7178 | 82 | if (cached) { |
7179 | 1 | return eval_ok(value_deep_clone(cached)); |
7180 | 1 | } |
7181 | | |
7182 | | /* Check for circular imports */ |
7183 | 81 | if (lat_map_get(&ev->required_files, resolved)) { |
7184 | 0 | char errbuf[512]; |
7185 | 0 | snprintf(errbuf, sizeof(errbuf), "import: circular dependency on '%s'", resolved); |
7186 | 0 | return eval_err(strdup(errbuf)); |
7187 | 0 | } |
7188 | | |
7189 | | /* Mark as loading */ |
7190 | 81 | bool marker = true; |
7191 | 81 | lat_map_set(&ev->required_files, resolved, &marker); |
7192 | | |
7193 | | /* Read the file */ |
7194 | 81 | char *source = builtin_read_file(resolved); |
7195 | 81 | if (!source) { |
7196 | 0 | char errbuf[512]; |
7197 | 0 | snprintf(errbuf, sizeof(errbuf), "import: cannot read '%s'", resolved); |
7198 | 0 | return eval_err(strdup(errbuf)); |
7199 | 0 | } |
7200 | | |
7201 | | /* Lex */ |
7202 | 81 | Lexer mod_lex = lexer_new(source); |
7203 | 81 | char *mod_lex_err = NULL; |
7204 | 81 | LatVec mod_toks = lexer_tokenize(&mod_lex, &mod_lex_err); |
7205 | 81 | free(source); |
7206 | 81 | if (mod_lex_err) { |
7207 | 0 | char errbuf[1024]; |
7208 | 0 | snprintf(errbuf, sizeof(errbuf), "import '%s': %s", resolved, mod_lex_err); |
7209 | 0 | free(mod_lex_err); |
7210 | 0 | return eval_err(strdup(errbuf)); |
7211 | 0 | } |
7212 | | |
7213 | | /* Parse */ |
7214 | 81 | Parser mod_parser = parser_new(&mod_toks); |
7215 | 81 | char *mod_parse_err = NULL; |
7216 | 81 | Program mod_prog = parser_parse(&mod_parser, &mod_parse_err); |
7217 | 81 | if (mod_parse_err) { |
7218 | 0 | char errbuf[1024]; |
7219 | 0 | snprintf(errbuf, sizeof(errbuf), "import '%s': %s", resolved, mod_parse_err); |
7220 | 0 | free(mod_parse_err); |
7221 | 0 | program_free(&mod_prog); |
7222 | 0 | for (size_t j = 0; j < mod_toks.len; j++) token_free(lat_vec_get(&mod_toks, j)); |
7223 | 0 | lat_vec_free(&mod_toks); |
7224 | 0 | return eval_err(strdup(errbuf)); |
7225 | 0 | } |
7226 | | |
7227 | | /* Register functions, structs, enums, traits, impls globally */ |
7228 | 1.85k | for (size_t j = 0; j < mod_prog.item_count; j++) { |
7229 | 1.77k | if (mod_prog.items[j].tag == ITEM_STRUCT) { |
7230 | 2 | StructDecl *ptr = &mod_prog.items[j].as.struct_decl; |
7231 | 2 | lat_map_set(&ev->struct_defs, ptr->name, &ptr); |
7232 | 1.77k | } else if (mod_prog.items[j].tag == ITEM_FUNCTION) { |
7233 | 1.75k | FnDecl *ptr = &mod_prog.items[j].as.fn_decl; |
7234 | 1.75k | register_fn_overload(&ev->fn_defs, ptr); |
7235 | 1.75k | } else if (mod_prog.items[j].tag == ITEM_ENUM) { |
7236 | 0 | EnumDecl *ptr = &mod_prog.items[j].as.enum_decl; |
7237 | 0 | lat_map_set(&ev->enum_defs, ptr->name, &ptr); |
7238 | 18 | } else if (mod_prog.items[j].tag == ITEM_TRAIT) { |
7239 | 0 | TraitDecl *ptr = &mod_prog.items[j].as.trait_decl; |
7240 | 0 | lat_map_set(&ev->trait_defs, ptr->name, &ptr); |
7241 | 18 | } else if (mod_prog.items[j].tag == ITEM_IMPL) { |
7242 | 0 | ImplBlock *ptr = &mod_prog.items[j].as.impl_block; |
7243 | 0 | char key[512]; |
7244 | 0 | snprintf(key, sizeof(key), "%s::%s", ptr->type_name, ptr->trait_name); |
7245 | 0 | lat_map_set(&ev->impl_registry, key, &ptr); |
7246 | 0 | } |
7247 | 1.77k | } |
7248 | | |
7249 | | /* Push a module scope and execute statements */ |
7250 | 81 | env_push_scope(ev->env); |
7251 | | |
7252 | 81 | char *prev_script_dir = ev->script_dir; |
7253 | 81 | char *resolved_copy = strdup(resolved); |
7254 | 81 | ev->script_dir = strdup(dirname(resolved_copy)); |
7255 | 81 | free(resolved_copy); |
7256 | | |
7257 | 81 | EvalResult exec_r = eval_ok(value_unit()); |
7258 | 1.85k | for (size_t j = 0; j < mod_prog.item_count; j++) { |
7259 | 1.77k | if (mod_prog.items[j].tag == ITEM_STMT) { |
7260 | 18 | value_free(&exec_r.value); |
7261 | 18 | exec_r = eval_stmt(ev, mod_prog.items[j].as.stmt); |
7262 | 18 | if (!IS_OK(exec_r)) break; |
7263 | 18 | } |
7264 | 1.77k | } |
7265 | | |
7266 | 81 | free(ev->script_dir); |
7267 | 81 | ev->script_dir = prev_script_dir; |
7268 | | |
7269 | 81 | if (!IS_OK(exec_r)) { |
7270 | 0 | env_pop_scope(ev->env); |
7271 | 0 | for (size_t j = 0; j < mod_toks.len; j++) token_free(lat_vec_get(&mod_toks, j)); |
7272 | 0 | lat_vec_free(&mod_toks); |
7273 | | /* Don't free prog items since decls may be registered */ |
7274 | 0 | return exec_r; |
7275 | 0 | } |
7276 | 81 | value_free(&exec_r.value); |
7277 | | |
7278 | | /* Build module Map from scope bindings and functions */ |
7279 | 81 | LatValue module_map = value_map_new(); |
7280 | | |
7281 | | /* Export top-level variable bindings from module scope */ |
7282 | 81 | Scope *mod_scope = &ev->env->scopes[ev->env->count - 1]; |
7283 | 1.37k | for (size_t i = 0; i < mod_scope->cap; i++) { |
7284 | 1.29k | if (mod_scope->entries[i].state == MAP_OCCUPIED) { |
7285 | 18 | const char *name = mod_scope->entries[i].key; |
7286 | 18 | if (!module_should_export(name, |
7287 | 18 | (const char **)mod_prog.export_names, |
7288 | 18 | mod_prog.export_count, mod_prog.has_exports)) |
7289 | 5 | continue; |
7290 | 13 | LatValue *val_ptr = (LatValue *)mod_scope->entries[i].value; |
7291 | 13 | LatValue exported = value_deep_clone(val_ptr); |
7292 | 13 | lat_map_set(module_map.as.map.map, name, &exported); |
7293 | 13 | } |
7294 | 1.29k | } |
7295 | | |
7296 | | /* Export functions as closures */ |
7297 | 1.85k | for (size_t j = 0; j < mod_prog.item_count; j++) { |
7298 | 1.77k | if (mod_prog.items[j].tag == ITEM_FUNCTION) { |
7299 | 1.75k | FnDecl *fn = &mod_prog.items[j].as.fn_decl; |
7300 | | |
7301 | | /* Filter based on export declarations */ |
7302 | 1.75k | if (!module_should_export(fn->name, |
7303 | 1.75k | (const char **)mod_prog.export_names, |
7304 | 1.75k | mod_prog.export_count, mod_prog.has_exports)) |
7305 | 5 | continue; |
7306 | | |
7307 | | /* Create an expr_block wrapping the function body. |
7308 | | * This borrows fn->body (kept alive via program items). */ |
7309 | 1.74k | Expr *body = calloc(1, sizeof(Expr)); |
7310 | 1.74k | body->tag = EXPR_BLOCK; |
7311 | 1.74k | body->as.block.stmts = fn->body; |
7312 | 1.74k | body->as.block.count = fn->body_count; |
7313 | | /* Track this Expr so it stays alive */ |
7314 | 1.74k | lat_vec_push(&ev->module_exprs, &body); |
7315 | | |
7316 | 1.74k | Env *captured = env_clone(ev->env); |
7317 | 1.74k | Expr **defaults = NULL; |
7318 | 1.74k | bool has_variadic = false; |
7319 | 1.74k | if (fn->param_count > 0) { |
7320 | 1.64k | defaults = malloc(fn->param_count * sizeof(Expr *)); |
7321 | 4.43k | for (size_t k = 0; k < fn->param_count; k++) { |
7322 | 2.78k | defaults[k] = fn->params[k].default_value; |
7323 | 2.78k | if (fn->params[k].is_variadic) has_variadic = true; |
7324 | 2.78k | } |
7325 | 1.64k | } |
7326 | | |
7327 | 1.74k | char **param_names = malloc(fn->param_count * sizeof(char *)); |
7328 | 4.53k | for (size_t k = 0; k < fn->param_count; k++) { |
7329 | 2.78k | param_names[k] = fn->params[k].name; |
7330 | 2.78k | } |
7331 | 1.74k | LatValue closure = value_closure(param_names, fn->param_count, body, |
7332 | 1.74k | captured, defaults, has_variadic); |
7333 | 1.74k | free(param_names); |
7334 | | /* Don't free defaults — value_closure borrows it. Track for cleanup. */ |
7335 | 1.74k | if (defaults) lat_vec_push(&ev->module_exprs, &defaults); |
7336 | 1.74k | lat_map_set(module_map.as.map.map, fn->name, &closure); |
7337 | 1.74k | } |
7338 | 1.77k | } |
7339 | | |
7340 | 81 | env_pop_scope(ev->env); |
7341 | | |
7342 | | /* Cache the module */ |
7343 | 81 | LatValue cached_copy = value_deep_clone(&module_map); |
7344 | 81 | lat_map_set(&ev->module_cache, resolved, &cached_copy); |
7345 | | |
7346 | | /* Cleanup: free statements, keep decl items alive */ |
7347 | 1.85k | for (size_t j = 0; j < mod_prog.item_count; j++) { |
7348 | 1.77k | if (mod_prog.items[j].tag == ITEM_STMT) |
7349 | 18 | stmt_free(mod_prog.items[j].as.stmt); |
7350 | 1.77k | } |
7351 | | /* Don't free prog.items since fn/struct/enum decls are still referenced */ |
7352 | | |
7353 | 117k | for (size_t j = 0; j < mod_toks.len; j++) token_free(lat_vec_get(&mod_toks, j)); |
7354 | 81 | lat_vec_free(&mod_toks); |
7355 | | |
7356 | 81 | return eval_ok(module_map); |
7357 | 81 | } |
7358 | | |
7359 | | /* ── Statement evaluation ── */ |
7360 | | |
7361 | 15.1k | static EvalResult eval_stmt(Evaluator *ev, const Stmt *stmt) { |
7362 | 15.1k | switch (stmt->tag) { |
7363 | 4.69k | case STMT_BINDING: { |
7364 | 4.69k | EvalResult vr = eval_expr(ev, stmt->as.binding.value); |
7365 | 4.69k | if (!IS_OK(vr)) return vr; |
7366 | | |
7367 | 4.67k | if (ev->mode == MODE_CASUAL) { |
7368 | 4.66k | switch (stmt->as.binding.phase) { |
7369 | 1.84k | case PHASE_FLUID: vr.value.phase = VTAG_FLUID; break; |
7370 | 20 | case PHASE_CRYSTAL: |
7371 | 20 | stats_freeze(&ev->stats); |
7372 | 20 | { uint64_t ft0 = now_ns(); |
7373 | 20 | vr.value = value_freeze(vr.value); |
7374 | 20 | freeze_to_region(ev, &vr.value); |
7375 | 20 | ev->stats.freeze_total_ns += now_ns() - ft0; } |
7376 | 20 | break; |
7377 | 2.80k | case PHASE_UNSPECIFIED: break; |
7378 | 4.66k | } |
7379 | 4.66k | } else { /* MODE_STRICT */ |
7380 | 9 | switch (stmt->as.binding.phase) { |
7381 | 5 | case PHASE_FLUID: |
7382 | 5 | if (value_is_crystal(&vr.value)) { |
7383 | 0 | char *err = NULL; |
7384 | 0 | (void)asprintf(&err, "strict mode: 'flux' binding '%s' produced a crystal value", |
7385 | 0 | stmt->as.binding.name); |
7386 | 0 | value_free(&vr.value); |
7387 | 0 | return eval_err(err); |
7388 | 0 | } |
7389 | 5 | vr.value.phase = VTAG_FLUID; |
7390 | 5 | break; |
7391 | 4 | case PHASE_CRYSTAL: |
7392 | 4 | stats_freeze(&ev->stats); |
7393 | 4 | { uint64_t ft0 = now_ns(); |
7394 | 4 | vr.value = value_freeze(vr.value); |
7395 | 4 | freeze_to_region(ev, &vr.value); |
7396 | 4 | ev->stats.freeze_total_ns += now_ns() - ft0; } |
7397 | 4 | break; |
7398 | 0 | case PHASE_UNSPECIFIED: { |
7399 | 0 | char *err = NULL; |
7400 | 0 | (void)asprintf(&err, "strict mode: binding '%s' requires an explicit phase (flux/fix)", |
7401 | 0 | stmt->as.binding.name); |
7402 | 0 | value_free(&vr.value); |
7403 | 0 | return eval_err(err); |
7404 | 5 | } |
7405 | 9 | } |
7406 | 9 | } |
7407 | 4.67k | stats_binding(&ev->stats); |
7408 | | /* In lat_eval context, top-level bindings go to the root scope |
7409 | | * so they persist across calls (needed for REPL) */ |
7410 | 4.67k | if (ev->lat_eval_scope > 0 && ev->env->count == ev->lat_eval_scope) |
7411 | 3 | env_define_at(ev->env, 0, stmt->as.binding.name, vr.value); |
7412 | 4.67k | else |
7413 | 4.67k | env_define(ev->env, stmt->as.binding.name, vr.value); |
7414 | 4.67k | return eval_ok(value_unit()); |
7415 | 4.67k | } |
7416 | | |
7417 | 5.21k | case STMT_ASSIGN: { |
7418 | 5.21k | EvalResult valr = eval_expr(ev, stmt->as.assign.value); |
7419 | 5.21k | if (!IS_OK(valr)) return valr; |
7420 | | |
7421 | | /* Simple ident assignment uses env_set for proper scoping */ |
7422 | 5.21k | if (stmt->as.assign.target->tag == EXPR_IDENT) { |
7423 | 5.16k | const char *name = stmt->as.assign.target->as.str_val; |
7424 | 5.16k | if (ev->mode == MODE_STRICT) { |
7425 | 1 | LatValue existing; |
7426 | 1 | if (env_get(ev->env, name, &existing)) { |
7427 | 1 | bool is_crys = value_is_crystal(&existing); |
7428 | 1 | value_free(&existing); |
7429 | 1 | if (is_crys) { |
7430 | 0 | char *err = NULL; |
7431 | 0 | (void)asprintf(&err, "strict mode: cannot assign to crystal binding '%s'", name); |
7432 | 0 | value_free(&valr.value); |
7433 | 0 | return eval_err(err); |
7434 | 0 | } |
7435 | 1 | } |
7436 | 1 | } |
7437 | 5.16k | if (!env_set(ev->env, name, valr.value)) { |
7438 | 0 | char *err = NULL; |
7439 | 0 | (void)asprintf(&err, "undefined variable '%s'", name); |
7440 | 0 | return eval_err(err); |
7441 | 0 | } |
7442 | 5.16k | record_history(ev, name); |
7443 | 5.16k | return eval_ok(value_unit()); |
7444 | 5.16k | } |
7445 | | |
7446 | | /* Buffer index assignment: buf[i] = byte (must be handled before resolve_lvalue |
7447 | | * since buffers store raw bytes, not LatValues) */ |
7448 | 49 | if (stmt->as.assign.target->tag == EXPR_INDEX) { |
7449 | 35 | char *buf_chk_err = NULL; |
7450 | 35 | LatValue *buf_chk = resolve_lvalue(ev, stmt->as.assign.target->as.index.object, &buf_chk_err); |
7451 | 35 | if (buf_chk_err) free(buf_chk_err); |
7452 | 35 | if (buf_chk && buf_chk->type == VAL_BUFFER) { |
7453 | 2 | EvalResult buf_idxr = eval_expr(ev, stmt->as.assign.target->as.index.index); |
7454 | 2 | if (!IS_OK(buf_idxr)) { value_free(&valr.value); return buf_idxr; } |
7455 | 2 | if (buf_idxr.value.type != VAL_INT) { |
7456 | 0 | value_free(&buf_idxr.value); value_free(&valr.value); |
7457 | 0 | return eval_err(strdup("buffer index must be an integer")); |
7458 | 0 | } |
7459 | 2 | size_t bidx = (size_t)buf_idxr.value.as.int_val; |
7460 | 2 | value_free(&buf_idxr.value); |
7461 | 2 | if (bidx >= buf_chk->as.buffer.len) { |
7462 | 0 | value_free(&valr.value); |
7463 | 0 | char *berr = NULL; |
7464 | 0 | (void)asprintf(&berr, "buffer index %zu out of bounds (length %zu)", |
7465 | 0 | bidx, buf_chk->as.buffer.len); |
7466 | 0 | return eval_err(berr); |
7467 | 0 | } |
7468 | 2 | if (valr.value.type != VAL_INT) { |
7469 | 0 | value_free(&valr.value); |
7470 | 0 | return eval_err(strdup("buffer element must be an integer")); |
7471 | 0 | } |
7472 | 2 | buf_chk->as.buffer.data[bidx] = (uint8_t)(valr.value.as.int_val & 0xFF); |
7473 | 2 | value_free(&valr.value); |
7474 | 2 | return eval_ok(value_unit()); |
7475 | 2 | } |
7476 | 35 | } |
7477 | | |
7478 | | /* For field access, index, and nested chains: use resolve_lvalue */ |
7479 | 47 | char *lv_err = NULL; |
7480 | 47 | LatValue *target = resolve_lvalue(ev, stmt->as.assign.target, &lv_err); |
7481 | 47 | if (!target) { |
7482 | 0 | value_free(&valr.value); |
7483 | 0 | return eval_err(lv_err); |
7484 | 0 | } |
7485 | 47 | if (ev->mode == MODE_STRICT && value_is_crystal(target)) { |
7486 | 0 | value_free(&valr.value); |
7487 | 0 | return eval_err(strdup("strict mode: cannot assign to crystal value")); |
7488 | 0 | } |
7489 | | /* Check sublimated phase for direct parent */ |
7490 | 47 | if (stmt->as.assign.target->tag == EXPR_FIELD_ACCESS) { |
7491 | 14 | LatValue *parent = resolve_lvalue(ev, stmt->as.assign.target->as.field_access.object, &lv_err); |
7492 | 14 | if (parent && parent->phase == VTAG_SUBLIMATED) { |
7493 | 0 | const char *fname = stmt->as.assign.target->as.field_access.field; |
7494 | 0 | char *err = NULL; |
7495 | 0 | (void)asprintf(&err, "cannot assign to field '%s' of sublimated value", fname); |
7496 | 0 | value_free(&valr.value); |
7497 | 0 | return eval_err(err); |
7498 | 0 | } |
7499 | 14 | } |
7500 | 47 | if (stmt->as.assign.target->tag == EXPR_INDEX) { |
7501 | 33 | LatValue *parent = resolve_lvalue(ev, stmt->as.assign.target->as.index.object, &lv_err); |
7502 | 33 | if (parent && parent->phase == VTAG_SUBLIMATED) { |
7503 | 1 | value_free(&valr.value); |
7504 | 1 | return eval_err(strdup("cannot assign to index of sublimated value")); |
7505 | 1 | } |
7506 | 33 | } |
7507 | | /* Check per-field phase for struct field assignments */ |
7508 | 46 | if (stmt->as.assign.target->tag == EXPR_FIELD_ACCESS) { |
7509 | 14 | LatValue *parent = resolve_lvalue(ev, stmt->as.assign.target->as.field_access.object, &lv_err); |
7510 | 14 | if (parent && parent->type == VAL_STRUCT && parent->phase == VTAG_CRYSTAL) { |
7511 | 1 | const char *fname = stmt->as.assign.target->as.field_access.field; |
7512 | 1 | char *err = NULL; |
7513 | 1 | (void)asprintf(&err, "cannot assign to field '%s' of frozen struct", fname); |
7514 | 1 | value_free(&valr.value); |
7515 | 1 | return eval_err(err); |
7516 | 1 | } |
7517 | 13 | if (parent && parent->type == VAL_STRUCT && parent->as.strct.field_phases) { |
7518 | 7 | const char *fname = stmt->as.assign.target->as.field_access.field; |
7519 | 12 | for (size_t fi = 0; fi < parent->as.strct.field_count; fi++) { |
7520 | 12 | if (strcmp(parent->as.strct.field_names[fi], fname) == 0) { |
7521 | 7 | if (parent->as.strct.field_phases[fi] == VTAG_CRYSTAL) { |
7522 | 3 | char *err = NULL; |
7523 | 3 | (void)asprintf(&err, "cannot assign to frozen field '%s'", fname); |
7524 | 3 | value_free(&valr.value); |
7525 | 3 | return eval_err(err); |
7526 | 3 | } |
7527 | 4 | break; |
7528 | 7 | } |
7529 | 12 | } |
7530 | 7 | } |
7531 | 13 | } |
7532 | | /* Check per-key phase for map key assignments */ |
7533 | 42 | if (stmt->as.assign.target->tag == EXPR_INDEX) { |
7534 | 32 | LatValue *parent = resolve_lvalue(ev, stmt->as.assign.target->as.index.object, &lv_err); |
7535 | 32 | if (parent && parent->type == VAL_REF && parent->phase == VTAG_CRYSTAL) { |
7536 | 1 | value_free(&valr.value); |
7537 | 1 | return eval_err(strdup("cannot assign index on a frozen Ref")); |
7538 | 1 | } |
7539 | 31 | if (parent && parent->type == VAL_MAP && parent->phase == VTAG_CRYSTAL) { |
7540 | 0 | value_free(&valr.value); |
7541 | 0 | return eval_err(strdup("cannot assign to key of frozen map")); |
7542 | 0 | } |
7543 | 31 | if (parent && parent->type == VAL_MAP && parent->as.map.key_phases) { |
7544 | 3 | EvalResult kidxr = eval_expr(ev, stmt->as.assign.target->as.index.index); |
7545 | 3 | if (IS_OK(kidxr) && kidxr.value.type == VAL_STR) { |
7546 | 3 | PhaseTag *kp = (PhaseTag *)lat_map_get(parent->as.map.key_phases, kidxr.value.as.str_val); |
7547 | 3 | if (kp && *kp == VTAG_CRYSTAL) { |
7548 | 1 | char *err = NULL; |
7549 | 1 | (void)asprintf(&err, "cannot assign to frozen key '%s'", kidxr.value.as.str_val); |
7550 | 1 | value_free(&kidxr.value); |
7551 | 1 | value_free(&valr.value); |
7552 | 1 | return eval_err(err); |
7553 | 1 | } |
7554 | 3 | } |
7555 | 2 | value_free(&kidxr.value); |
7556 | 2 | } |
7557 | 31 | } |
7558 | 40 | value_free(target); |
7559 | 40 | *target = valr.value; |
7560 | | /* Record history for root variable of field/index chain */ |
7561 | 40 | if (ev->tracked_count > 0) { |
7562 | 0 | const Expr *root = stmt->as.assign.target; |
7563 | 0 | while (root->tag == EXPR_FIELD_ACCESS) root = root->as.field_access.object; |
7564 | 0 | while (root->tag == EXPR_INDEX) root = root->as.index.object; |
7565 | 0 | if (root->tag == EXPR_IDENT) |
7566 | 0 | record_history(ev, root->as.str_val); |
7567 | 0 | } |
7568 | 40 | return eval_ok(value_unit()); |
7569 | 42 | } |
7570 | | |
7571 | 4.16k | case STMT_EXPR: |
7572 | 4.16k | return eval_expr(ev, stmt->as.expr); |
7573 | | |
7574 | 783 | case STMT_RETURN: { |
7575 | 783 | if (stmt->as.return_expr) { |
7576 | 782 | EvalResult er = eval_expr(ev, stmt->as.return_expr); |
7577 | 782 | if (!IS_OK(er)) return er; |
7578 | 782 | return eval_signal(CF_RETURN, er.value); |
7579 | 782 | } |
7580 | 1 | return eval_signal(CF_RETURN, value_unit()); |
7581 | 783 | } |
7582 | | |
7583 | 59 | case STMT_FOR: { |
7584 | 59 | EvalResult iter_r = eval_expr(ev, stmt->as.for_loop.iter); |
7585 | 59 | if (!IS_OK(iter_r)) return iter_r; |
7586 | | |
7587 | 59 | if (iter_r.value.type == VAL_RANGE) { |
7588 | 20 | int64_t s = iter_r.value.as.range.start; |
7589 | 20 | int64_t e = iter_r.value.as.range.end; |
7590 | 20 | value_free(&iter_r.value); |
7591 | 654 | for (int64_t i = s; i < e; i++) { |
7592 | 634 | stats_scope_push(&ev->stats); |
7593 | 634 | env_push_scope(ev->env); |
7594 | 634 | env_define(ev->env, stmt->as.for_loop.var, value_int(i)); |
7595 | 634 | EvalResult r = eval_block_stmts(ev, stmt->as.for_loop.body, |
7596 | 634 | stmt->as.for_loop.body_count); |
7597 | 634 | env_pop_scope(ev->env); |
7598 | 634 | stats_scope_pop(&ev->stats); |
7599 | 634 | if (IS_SIGNAL(r) && r.cf.tag == CF_BREAK) break; |
7600 | 634 | if (IS_SIGNAL(r) && r.cf.tag == CF_CONTINUE) continue; |
7601 | 629 | if (!IS_OK(r)) return r; |
7602 | 629 | value_free(&r.value); |
7603 | 629 | } |
7604 | 39 | } else if (iter_r.value.type == VAL_ARRAY) { |
7605 | 37 | GC_PUSH(ev, &iter_r.value); |
7606 | 37 | size_t len = iter_r.value.as.array.len; |
7607 | 112 | for (size_t i = 0; i < len; i++) { |
7608 | 75 | stats_scope_push(&ev->stats); |
7609 | 75 | env_push_scope(ev->env); |
7610 | 75 | LatValue elem = value_deep_clone(&iter_r.value.as.array.elems[i]); |
7611 | 75 | env_define(ev->env, stmt->as.for_loop.var, elem); |
7612 | 75 | EvalResult r = eval_block_stmts(ev, stmt->as.for_loop.body, |
7613 | 75 | stmt->as.for_loop.body_count); |
7614 | 75 | env_pop_scope(ev->env); |
7615 | 75 | stats_scope_pop(&ev->stats); |
7616 | 75 | if (IS_SIGNAL(r) && r.cf.tag == CF_BREAK) break; |
7617 | 75 | if (IS_SIGNAL(r) && r.cf.tag == CF_CONTINUE) continue; |
7618 | 75 | if (!IS_OK(r)) { GC_POP(ev); value_free(&iter_r.value); return r; } |
7619 | 75 | value_free(&r.value); |
7620 | 75 | } |
7621 | 37 | GC_POP(ev); |
7622 | 37 | value_free(&iter_r.value); |
7623 | 37 | } else if (iter_r.value.type == VAL_MAP) { |
7624 | | /* Iterate over map keys */ |
7625 | 1 | GC_PUSH(ev, &iter_r.value); |
7626 | 17 | for (size_t i = 0; i < iter_r.value.as.map.map->cap; i++) { |
7627 | 16 | if (iter_r.value.as.map.map->entries[i].state != MAP_OCCUPIED) continue; |
7628 | 1 | stats_scope_push(&ev->stats); |
7629 | 1 | env_push_scope(ev->env); |
7630 | 1 | env_define(ev->env, stmt->as.for_loop.var, |
7631 | 1 | value_string(iter_r.value.as.map.map->entries[i].key)); |
7632 | 1 | EvalResult r = eval_block_stmts(ev, stmt->as.for_loop.body, |
7633 | 1 | stmt->as.for_loop.body_count); |
7634 | 1 | env_pop_scope(ev->env); |
7635 | 1 | stats_scope_pop(&ev->stats); |
7636 | 1 | if (IS_SIGNAL(r) && r.cf.tag == CF_BREAK) break; |
7637 | 1 | if (IS_SIGNAL(r) && r.cf.tag == CF_CONTINUE) continue; |
7638 | 1 | if (!IS_OK(r)) { GC_POP(ev); value_free(&iter_r.value); return r; } |
7639 | 1 | value_free(&r.value); |
7640 | 1 | } |
7641 | 1 | GC_POP(ev); |
7642 | 1 | value_free(&iter_r.value); |
7643 | 1 | } else if (iter_r.value.type == VAL_SET) { |
7644 | | /* Iterate over set elements */ |
7645 | 1 | GC_PUSH(ev, &iter_r.value); |
7646 | 17 | for (size_t i = 0; i < iter_r.value.as.set.map->cap; i++) { |
7647 | 16 | if (iter_r.value.as.set.map->entries[i].state != MAP_OCCUPIED) continue; |
7648 | 1 | stats_scope_push(&ev->stats); |
7649 | 1 | env_push_scope(ev->env); |
7650 | 1 | LatValue *sv = (LatValue *)iter_r.value.as.set.map->entries[i].value; |
7651 | 1 | env_define(ev->env, stmt->as.for_loop.var, value_deep_clone(sv)); |
7652 | 1 | EvalResult r = eval_block_stmts(ev, stmt->as.for_loop.body, |
7653 | 1 | stmt->as.for_loop.body_count); |
7654 | 1 | env_pop_scope(ev->env); |
7655 | 1 | stats_scope_pop(&ev->stats); |
7656 | 1 | if (IS_SIGNAL(r) && r.cf.tag == CF_BREAK) break; |
7657 | 1 | if (IS_SIGNAL(r) && r.cf.tag == CF_CONTINUE) continue; |
7658 | 1 | if (!IS_OK(r)) { GC_POP(ev); value_free(&iter_r.value); return r; } |
7659 | 1 | value_free(&r.value); |
7660 | 1 | } |
7661 | 1 | GC_POP(ev); |
7662 | 1 | value_free(&iter_r.value); |
7663 | 1 | } else { |
7664 | 0 | char *err = NULL; |
7665 | 0 | (void)asprintf(&err, "cannot iterate over %s", value_type_name(&iter_r.value)); |
7666 | 0 | value_free(&iter_r.value); |
7667 | 0 | return eval_err(err); |
7668 | 0 | } |
7669 | 59 | return eval_ok(value_unit()); |
7670 | 59 | } |
7671 | | |
7672 | 32 | case STMT_WHILE: { |
7673 | 3.14k | for (;;) { |
7674 | 3.14k | EvalResult condr = eval_expr(ev, stmt->as.while_loop.cond); |
7675 | 3.14k | if (!IS_OK(condr)) return condr; |
7676 | 3.14k | bool truthy = value_is_truthy(&condr.value); |
7677 | 3.14k | value_free(&condr.value); |
7678 | 3.14k | if (!truthy) break; |
7679 | 3.11k | stats_scope_push(&ev->stats); |
7680 | 3.11k | env_push_scope(ev->env); |
7681 | 3.11k | EvalResult r = eval_block_stmts(ev, stmt->as.while_loop.body, |
7682 | 3.11k | stmt->as.while_loop.body_count); |
7683 | 3.11k | env_pop_scope(ev->env); |
7684 | 3.11k | stats_scope_pop(&ev->stats); |
7685 | 3.11k | if (IS_SIGNAL(r) && r.cf.tag == CF_BREAK) break; |
7686 | 3.11k | if (IS_SIGNAL(r) && r.cf.tag == CF_CONTINUE) continue; |
7687 | 3.11k | if (!IS_OK(r)) return r; |
7688 | 3.11k | value_free(&r.value); |
7689 | 3.11k | } |
7690 | 32 | return eval_ok(value_unit()); |
7691 | 32 | } |
7692 | | |
7693 | 26 | case STMT_LOOP: { |
7694 | 195 | for (;;) { |
7695 | 195 | stats_scope_push(&ev->stats); |
7696 | 195 | env_push_scope(ev->env); |
7697 | 195 | EvalResult r = eval_block_stmts(ev, stmt->as.loop.body, stmt->as.loop.body_count); |
7698 | 195 | env_pop_scope(ev->env); |
7699 | 195 | stats_scope_pop(&ev->stats); |
7700 | 195 | if (IS_SIGNAL(r) && r.cf.tag == CF_BREAK) break; |
7701 | 181 | if (IS_SIGNAL(r) && r.cf.tag == CF_CONTINUE) continue; |
7702 | 181 | if (!IS_OK(r)) return r; |
7703 | 169 | value_free(&r.value); |
7704 | 169 | } |
7705 | 14 | return eval_ok(value_unit()); |
7706 | 26 | } |
7707 | | |
7708 | 18 | case STMT_BREAK: return eval_signal(CF_BREAK, value_unit()); |
7709 | 7 | case STMT_CONTINUE: return eval_signal(CF_CONTINUE, value_unit()); |
7710 | | |
7711 | 8 | case STMT_DEFER: { |
7712 | | /* Push to defer stack — don't execute now */ |
7713 | 8 | if (ev->defer_count >= ev->defer_cap) { |
7714 | 4 | ev->defer_cap = ev->defer_cap < 8 ? 8 : ev->defer_cap * 2; |
7715 | 4 | ev->defer_stack = realloc(ev->defer_stack, ev->defer_cap * sizeof(DeferEntry)); |
7716 | 4 | } |
7717 | 8 | ev->defer_stack[ev->defer_count].body = stmt->as.defer.body; |
7718 | 8 | ev->defer_stack[ev->defer_count].body_count = stmt->as.defer.body_count; |
7719 | 8 | ev->defer_stack[ev->defer_count].scope_depth = ev->stats.current_scope_depth; |
7720 | 8 | ev->defer_count++; |
7721 | 8 | return eval_ok(value_unit()); |
7722 | 26 | } |
7723 | | |
7724 | 8 | case STMT_DESTRUCTURE: { |
7725 | 8 | EvalResult vr = eval_expr(ev, stmt->as.destructure.value); |
7726 | 8 | if (!IS_OK(vr)) return vr; |
7727 | | |
7728 | 8 | if (stmt->as.destructure.kind == DESTRUCT_ARRAY) { |
7729 | 6 | if (vr.value.type != VAL_ARRAY) { |
7730 | 0 | char *err = NULL; |
7731 | 0 | (void)asprintf(&err, "cannot destructure %s as array", |
7732 | 0 | value_type_name(&vr.value)); |
7733 | 0 | value_free(&vr.value); |
7734 | 0 | return eval_err(err); |
7735 | 0 | } |
7736 | 6 | size_t arr_len = vr.value.as.array.len; |
7737 | 6 | size_t name_count = stmt->as.destructure.name_count; |
7738 | 6 | bool has_rest = (stmt->as.destructure.rest_name != NULL); |
7739 | | |
7740 | 6 | if (!has_rest && arr_len != name_count) { |
7741 | 0 | char *err = NULL; |
7742 | 0 | (void)asprintf(&err, "array destructure: expected %zu elements, got %zu", |
7743 | 0 | name_count, arr_len); |
7744 | 0 | value_free(&vr.value); |
7745 | 0 | return eval_err(err); |
7746 | 0 | } |
7747 | 6 | if (has_rest && arr_len < name_count) { |
7748 | 0 | char *err = NULL; |
7749 | 0 | (void)asprintf(&err, "array destructure: expected at least %zu elements, got %zu", |
7750 | 0 | name_count, arr_len); |
7751 | 0 | value_free(&vr.value); |
7752 | 0 | return eval_err(err); |
7753 | 0 | } |
7754 | | |
7755 | | /* Bind named elements */ |
7756 | 18 | for (size_t i = 0; i < name_count; i++) { |
7757 | 12 | LatValue elem = value_deep_clone(&vr.value.as.array.elems[i]); |
7758 | | /* Apply phase */ |
7759 | 12 | if (stmt->as.destructure.phase == PHASE_FLUID) |
7760 | 2 | elem.phase = VTAG_FLUID; |
7761 | 10 | else if (stmt->as.destructure.phase == PHASE_CRYSTAL) { |
7762 | 0 | stats_freeze(&ev->stats); |
7763 | 0 | elem = value_freeze(elem); |
7764 | 0 | freeze_to_region(ev, &elem); |
7765 | 0 | } |
7766 | 12 | stats_binding(&ev->stats); |
7767 | 12 | env_define(ev->env, stmt->as.destructure.names[i], elem); |
7768 | 12 | } |
7769 | | |
7770 | | /* Bind rest */ |
7771 | 6 | if (has_rest) { |
7772 | 3 | size_t rest_count = arr_len - name_count; |
7773 | 3 | LatValue *rest_elems = malloc(rest_count * sizeof(LatValue)); |
7774 | 9 | for (size_t i = 0; i < rest_count; i++) |
7775 | 6 | rest_elems[i] = value_deep_clone(&vr.value.as.array.elems[name_count + i]); |
7776 | 3 | LatValue rest_arr = value_array(rest_elems, rest_count); |
7777 | 3 | free(rest_elems); |
7778 | 3 | if (stmt->as.destructure.phase == PHASE_FLUID) |
7779 | 0 | rest_arr.phase = VTAG_FLUID; |
7780 | 3 | else if (stmt->as.destructure.phase == PHASE_CRYSTAL) { |
7781 | 0 | stats_freeze(&ev->stats); |
7782 | 0 | rest_arr = value_freeze(rest_arr); |
7783 | 0 | freeze_to_region(ev, &rest_arr); |
7784 | 0 | } |
7785 | 3 | stats_binding(&ev->stats); |
7786 | 3 | env_define(ev->env, stmt->as.destructure.rest_name, rest_arr); |
7787 | 3 | } |
7788 | 6 | value_free(&vr.value); |
7789 | | |
7790 | 6 | } else { |
7791 | | /* DESTRUCT_STRUCT */ |
7792 | 2 | if (vr.value.type != VAL_STRUCT && vr.value.type != VAL_MAP) { |
7793 | 0 | char *err = NULL; |
7794 | 0 | (void)asprintf(&err, "cannot destructure %s as struct", |
7795 | 0 | value_type_name(&vr.value)); |
7796 | 0 | value_free(&vr.value); |
7797 | 0 | return eval_err(err); |
7798 | 0 | } |
7799 | | |
7800 | 6 | for (size_t i = 0; i < stmt->as.destructure.name_count; i++) { |
7801 | 4 | const char *fname = stmt->as.destructure.names[i]; |
7802 | 4 | LatValue elem = value_unit(); |
7803 | 4 | bool found = false; |
7804 | | |
7805 | 4 | if (vr.value.type == VAL_STRUCT) { |
7806 | 3 | for (size_t j = 0; j < vr.value.as.strct.field_count; j++) { |
7807 | 3 | if (strcmp(vr.value.as.strct.field_names[j], fname) == 0) { |
7808 | 2 | elem = value_deep_clone(&vr.value.as.strct.field_values[j]); |
7809 | 2 | found = true; |
7810 | 2 | break; |
7811 | 2 | } |
7812 | 3 | } |
7813 | 2 | } else { |
7814 | | /* VAL_MAP */ |
7815 | 2 | LatValue *mval = lat_map_get(vr.value.as.map.map, fname); |
7816 | 2 | if (mval) { |
7817 | 2 | elem = value_deep_clone(mval); |
7818 | 2 | found = true; |
7819 | 2 | } |
7820 | 2 | } |
7821 | | |
7822 | 4 | if (!found) { |
7823 | 0 | char *err = NULL; |
7824 | 0 | (void)asprintf(&err, "destructure: field '%s' not found", fname); |
7825 | 0 | value_free(&vr.value); |
7826 | 0 | return eval_err(err); |
7827 | 0 | } |
7828 | | |
7829 | 4 | if (stmt->as.destructure.phase == PHASE_FLUID) |
7830 | 0 | elem.phase = VTAG_FLUID; |
7831 | 4 | else if (stmt->as.destructure.phase == PHASE_CRYSTAL) { |
7832 | 0 | stats_freeze(&ev->stats); |
7833 | 0 | elem = value_freeze(elem); |
7834 | 0 | freeze_to_region(ev, &elem); |
7835 | 0 | } |
7836 | 4 | stats_binding(&ev->stats); |
7837 | 4 | env_define(ev->env, fname, elem); |
7838 | 4 | } |
7839 | 2 | value_free(&vr.value); |
7840 | 2 | } |
7841 | 8 | return eval_ok(value_unit()); |
7842 | 8 | } |
7843 | | |
7844 | 94 | case STMT_IMPORT: { |
7845 | 94 | const char *path = stmt->as.import.module_path; |
7846 | 94 | const char *alias = stmt->as.import.alias; |
7847 | 94 | char **selective = stmt->as.import.selective_names; |
7848 | 94 | size_t sel_count = stmt->as.import.selective_count; |
7849 | | |
7850 | 94 | EvalResult mod_r = load_module(ev, path); |
7851 | 94 | if (!IS_OK(mod_r)) return mod_r; |
7852 | | |
7853 | 93 | LatValue module_map = mod_r.value; |
7854 | | |
7855 | | /* Selective import: import { x, y } from "path" */ |
7856 | 93 | if (selective) { |
7857 | 35 | for (size_t i = 0; i < sel_count; i++) { |
7858 | 20 | const char *name = selective[i]; |
7859 | 20 | LatValue *exported = (LatValue *)lat_map_get(module_map.as.map.map, name); |
7860 | 20 | if (!exported) { |
7861 | 2 | char *err = NULL; |
7862 | 2 | (void)asprintf(&err, "module '%s' does not export '%s'", path, name); |
7863 | 2 | value_free(&module_map); |
7864 | 2 | return eval_err(err); |
7865 | 2 | } |
7866 | 18 | env_define(ev->env, name, value_deep_clone(exported)); |
7867 | 18 | } |
7868 | 15 | value_free(&module_map); |
7869 | 15 | return eval_ok(value_unit()); |
7870 | 17 | } |
7871 | | |
7872 | | /* Full import: import "path" as name */ |
7873 | 76 | if (!alias) { |
7874 | 0 | value_free(&module_map); |
7875 | 0 | return eval_err(strdup("import requires 'as <name>' or selective '{ ... } from'")); |
7876 | 0 | } |
7877 | | |
7878 | 76 | env_define(ev->env, alias, module_map); |
7879 | 76 | return eval_ok(value_unit()); |
7880 | 76 | } |
7881 | 15.1k | } |
7882 | 0 | return eval_err(strdup("unknown statement type")); |
7883 | 15.1k | } |
7884 | | |
7885 | | /* Run deferred blocks for the current scope depth (LIFO) */ |
7886 | 6.54k | static EvalResult run_defers_for_scope(Evaluator *ev, size_t scope_depth) { |
7887 | 6.54k | EvalResult first_err = { .ok = true, .error = NULL }; |
7888 | 6.54k | first_err.value = value_unit(); |
7889 | 6.54k | first_err.cf.tag = CF_NONE; |
7890 | 6.55k | while (ev->defer_count > 0 && ev->defer_stack[ev->defer_count - 1].scope_depth >= scope_depth) { |
7891 | 8 | DeferEntry de = ev->defer_stack[--ev->defer_count]; |
7892 | 8 | EvalResult dr = eval_block_stmts(ev, de.body, de.body_count); |
7893 | 8 | if (!IS_OK(dr) && first_err.ok) { |
7894 | 0 | first_err = dr; |
7895 | 8 | } else if (!IS_OK(dr)) { |
7896 | 0 | if (IS_ERR(dr)) free(dr.error); |
7897 | 0 | } |
7898 | 8 | } |
7899 | 6.54k | return first_err; |
7900 | 6.54k | } |
7901 | | |
7902 | 6.54k | static EvalResult eval_block_stmts(Evaluator *ev, Stmt **stmts, size_t count) { |
7903 | 6.54k | size_t defer_base = ev->defer_count; |
7904 | 6.54k | size_t scope_depth = ev->stats.current_scope_depth; |
7905 | 6.54k | LatValue last = value_unit(); |
7906 | 6.54k | GC_PUSH(ev, &last); |
7907 | 20.3k | for (size_t i = 0; i < count; i++) { |
7908 | 14.8k | gc_maybe_collect(ev); |
7909 | 14.8k | value_free(&last); |
7910 | 14.8k | EvalResult r = eval_stmt(ev, stmts[i]); |
7911 | 14.8k | if (!IS_OK(r)) { |
7912 | | /* Run defers before propagating error/signal */ |
7913 | 1.04k | EvalResult dr = run_defers_for_scope(ev, scope_depth); |
7914 | 1.04k | GC_POP(ev); |
7915 | 1.04k | if (!dr.ok && r.ok) return dr; |
7916 | 1.04k | if (!dr.ok && IS_ERR(dr)) free(dr.error); |
7917 | 1.04k | return r; |
7918 | 1.04k | } |
7919 | 13.7k | last = r.value; |
7920 | 13.7k | } |
7921 | | /* Run defers on normal exit */ |
7922 | 5.50k | EvalResult dr = run_defers_for_scope(ev, scope_depth); |
7923 | 5.50k | (void)defer_base; |
7924 | 5.50k | GC_POP(ev); |
7925 | 5.50k | if (!dr.ok) { value_free(&last); return dr; } |
7926 | 5.50k | return eval_ok(last); |
7927 | 5.50k | } |
7928 | | |
7929 | | /* ── Method calls ── */ |
7930 | | |
7931 | | static EvalResult eval_method_call(Evaluator *ev, LatValue obj, const char *method, |
7932 | 1.70k | LatValue *args, size_t arg_count) { |
7933 | | /* ── Enum methods ── */ |
7934 | 1.70k | if (obj.type == VAL_ENUM) { |
7935 | 6 | if (strcmp(method, "variant_name") == 0) { |
7936 | 1 | if (arg_count != 0) return eval_err(strdup("variant_name() takes no arguments")); |
7937 | 1 | return eval_ok(value_string(obj.as.enm.variant_name)); |
7938 | 1 | } |
7939 | 5 | if (strcmp(method, "enum_name") == 0) { |
7940 | 1 | if (arg_count != 0) return eval_err(strdup("enum_name() takes no arguments")); |
7941 | 1 | return eval_ok(value_string(obj.as.enm.enum_name)); |
7942 | 1 | } |
7943 | 4 | if (strcmp(method, "is_variant") == 0) { |
7944 | 2 | if (arg_count != 1) |
7945 | 0 | return eval_err(strdup("is_variant() expects 1 argument")); |
7946 | 2 | if (args[0].type != VAL_STR) |
7947 | 0 | return eval_err(strdup("is_variant() expects a String argument")); |
7948 | 2 | bool match = (strcmp(obj.as.enm.variant_name, args[0].as.str_val) == 0); |
7949 | 2 | return eval_ok(value_bool(match)); |
7950 | 2 | } |
7951 | 2 | if (strcmp(method, "payload") == 0) { |
7952 | 2 | if (arg_count != 0) return eval_err(strdup("payload() takes no arguments")); |
7953 | 2 | LatValue r; |
7954 | 2 | if (obj.as.enm.payload_count > 0) { |
7955 | 2 | LatValue *elems = malloc(obj.as.enm.payload_count * sizeof(LatValue)); |
7956 | 5 | for (size_t i = 0; i < obj.as.enm.payload_count; i++) |
7957 | 3 | elems[i] = value_deep_clone(&obj.as.enm.payload[i]); |
7958 | 2 | r = value_array(elems, obj.as.enm.payload_count); |
7959 | 2 | free(elems); |
7960 | 2 | } else { |
7961 | 0 | r = value_array(NULL, 0); |
7962 | 0 | } |
7963 | 2 | return eval_ok(r); |
7964 | 2 | } |
7965 | 0 | char *err2 = NULL; |
7966 | 0 | (void)asprintf(&err2, "Enum has no method '%s'", method); |
7967 | 0 | return eval_err(err2); |
7968 | 2 | } |
7969 | | |
7970 | | /* ── Set methods ── */ |
7971 | 1.70k | if (obj.type == VAL_SET) { |
7972 | | /// @method Set.has(value: Any) -> Bool |
7973 | | /// @category Set Methods |
7974 | | /// Check if the set contains the value. |
7975 | | /// @example s.has(42) |
7976 | 21 | if (strcmp(method, "has") == 0) { |
7977 | 7 | if (arg_count != 1) return eval_err(strdup(".has() expects 1 argument")); |
7978 | 7 | char *key = value_display(&args[0]); |
7979 | 7 | bool result = lat_map_contains(obj.as.set.map, key); |
7980 | 7 | free(key); |
7981 | 7 | return eval_ok(value_bool(result)); |
7982 | 7 | } |
7983 | | /// @method Set.len() -> Int |
7984 | | /// @category Set Methods |
7985 | | /// Return the number of elements in the set. |
7986 | | /// @example s.len() |
7987 | 14 | if (strcmp(method, "len") == 0) { |
7988 | 7 | if (arg_count != 0) return eval_err(strdup(".len() takes no arguments")); |
7989 | 7 | return eval_ok(value_int((int64_t)lat_map_len(obj.as.set.map))); |
7990 | 7 | } |
7991 | | /// @method Set.to_array() -> Array |
7992 | | /// @category Set Methods |
7993 | | /// Convert the set to an array of its elements. |
7994 | | /// @example s.to_array() |
7995 | 7 | if (strcmp(method, "to_array") == 0) { |
7996 | 1 | if (arg_count != 0) return eval_err(strdup(".to_array() takes no arguments")); |
7997 | 1 | size_t n = lat_map_len(obj.as.set.map); |
7998 | 1 | LatValue *elems = malloc((n > 0 ? n : 1) * sizeof(LatValue)); |
7999 | 1 | size_t ei = 0; |
8000 | 17 | for (size_t i = 0; i < obj.as.set.map->cap; i++) { |
8001 | 16 | if (obj.as.set.map->entries[i].state != MAP_OCCUPIED) continue; |
8002 | 1 | LatValue *sv = (LatValue *)obj.as.set.map->entries[i].value; |
8003 | 1 | elems[ei++] = value_deep_clone(sv); |
8004 | 1 | } |
8005 | 1 | LatValue arr = value_array(elems, ei); |
8006 | 1 | free(elems); |
8007 | 1 | return eval_ok(arr); |
8008 | 1 | } |
8009 | | /// @method Set.union(other: Set) -> Set |
8010 | | /// @category Set Methods |
8011 | | /// Return a new set containing all elements from both sets. |
8012 | | /// @example s1.union(s2) |
8013 | 6 | if (strcmp(method, "union") == 0) { |
8014 | 1 | if (arg_count != 1 || args[0].type != VAL_SET) |
8015 | 0 | return eval_err(strdup(".union() expects 1 Set argument")); |
8016 | 1 | LatValue result = value_set_new(); |
8017 | 17 | for (size_t i = 0; i < obj.as.set.map->cap; i++) { |
8018 | 16 | if (obj.as.set.map->entries[i].state == MAP_OCCUPIED) { |
8019 | 2 | LatValue *sv = (LatValue *)obj.as.set.map->entries[i].value; |
8020 | 2 | LatValue cloned = value_deep_clone(sv); |
8021 | 2 | lat_map_set(result.as.set.map, obj.as.set.map->entries[i].key, &cloned); |
8022 | 2 | } |
8023 | 16 | } |
8024 | 17 | for (size_t i = 0; i < args[0].as.set.map->cap; i++) { |
8025 | 16 | if (args[0].as.set.map->entries[i].state == MAP_OCCUPIED) { |
8026 | 2 | if (!lat_map_contains(result.as.set.map, args[0].as.set.map->entries[i].key)) { |
8027 | 1 | LatValue *sv = (LatValue *)args[0].as.set.map->entries[i].value; |
8028 | 1 | LatValue cloned = value_deep_clone(sv); |
8029 | 1 | lat_map_set(result.as.set.map, args[0].as.set.map->entries[i].key, &cloned); |
8030 | 1 | } |
8031 | 2 | } |
8032 | 16 | } |
8033 | 1 | return eval_ok(result); |
8034 | 1 | } |
8035 | | /// @method Set.intersection(other: Set) -> Set |
8036 | | /// @category Set Methods |
8037 | | /// Return a new set containing only elements in both sets. |
8038 | | /// @example s1.intersection(s2) |
8039 | 5 | if (strcmp(method, "intersection") == 0) { |
8040 | 1 | if (arg_count != 1 || args[0].type != VAL_SET) |
8041 | 0 | return eval_err(strdup(".intersection() expects 1 Set argument")); |
8042 | 1 | LatValue result = value_set_new(); |
8043 | 17 | for (size_t i = 0; i < obj.as.set.map->cap; i++) { |
8044 | 16 | if (obj.as.set.map->entries[i].state == MAP_OCCUPIED) { |
8045 | 3 | const char *key = obj.as.set.map->entries[i].key; |
8046 | 3 | if (lat_map_contains(args[0].as.set.map, key)) { |
8047 | 2 | LatValue *sv = (LatValue *)obj.as.set.map->entries[i].value; |
8048 | 2 | LatValue cloned = value_deep_clone(sv); |
8049 | 2 | lat_map_set(result.as.set.map, key, &cloned); |
8050 | 2 | } |
8051 | 3 | } |
8052 | 16 | } |
8053 | 1 | return eval_ok(result); |
8054 | 1 | } |
8055 | | /// @method Set.difference(other: Set) -> Set |
8056 | | /// @category Set Methods |
8057 | | /// Return a new set with elements in this set but not in other. |
8058 | | /// @example s1.difference(s2) |
8059 | 4 | if (strcmp(method, "difference") == 0) { |
8060 | 1 | if (arg_count != 1 || args[0].type != VAL_SET) |
8061 | 0 | return eval_err(strdup(".difference() expects 1 Set argument")); |
8062 | 1 | LatValue result = value_set_new(); |
8063 | 17 | for (size_t i = 0; i < obj.as.set.map->cap; i++) { |
8064 | 16 | if (obj.as.set.map->entries[i].state == MAP_OCCUPIED) { |
8065 | 3 | const char *key = obj.as.set.map->entries[i].key; |
8066 | 3 | if (!lat_map_contains(args[0].as.set.map, key)) { |
8067 | 1 | LatValue *sv = (LatValue *)obj.as.set.map->entries[i].value; |
8068 | 1 | LatValue cloned = value_deep_clone(sv); |
8069 | 1 | lat_map_set(result.as.set.map, key, &cloned); |
8070 | 1 | } |
8071 | 3 | } |
8072 | 16 | } |
8073 | 1 | return eval_ok(result); |
8074 | 1 | } |
8075 | | /// @method Set.is_subset(other: Set) -> Bool |
8076 | | /// @category Set Methods |
8077 | | /// Check if this set is a subset of other. |
8078 | | /// @example s1.is_subset(s2) |
8079 | 3 | if (strcmp(method, "is_subset") == 0) { |
8080 | 2 | if (arg_count != 1 || args[0].type != VAL_SET) |
8081 | 0 | return eval_err(strdup(".is_subset() expects 1 Set argument")); |
8082 | 20 | for (size_t i = 0; i < obj.as.set.map->cap; i++) { |
8083 | 19 | if (obj.as.set.map->entries[i].state == MAP_OCCUPIED) { |
8084 | 3 | if (!lat_map_contains(args[0].as.set.map, obj.as.set.map->entries[i].key)) |
8085 | 1 | return eval_ok(value_bool(false)); |
8086 | 3 | } |
8087 | 19 | } |
8088 | 1 | return eval_ok(value_bool(true)); |
8089 | 2 | } |
8090 | | /// @method Set.is_superset(other: Set) -> Bool |
8091 | | /// @category Set Methods |
8092 | | /// Check if this set is a superset of other. |
8093 | | /// @example s1.is_superset(s2) |
8094 | 1 | if (strcmp(method, "is_superset") == 0) { |
8095 | 1 | if (arg_count != 1 || args[0].type != VAL_SET) |
8096 | 0 | return eval_err(strdup(".is_superset() expects 1 Set argument")); |
8097 | 17 | for (size_t i = 0; i < args[0].as.set.map->cap; i++) { |
8098 | 16 | if (args[0].as.set.map->entries[i].state == MAP_OCCUPIED) { |
8099 | 2 | if (!lat_map_contains(obj.as.set.map, args[0].as.set.map->entries[i].key)) |
8100 | 0 | return eval_ok(value_bool(false)); |
8101 | 2 | } |
8102 | 16 | } |
8103 | 1 | return eval_ok(value_bool(true)); |
8104 | 1 | } |
8105 | 0 | char *err2 = NULL; |
8106 | 0 | (void)asprintf(&err2, "Set has no method '%s'", method); |
8107 | 0 | return eval_err(err2); |
8108 | 1 | } |
8109 | | |
8110 | | /* ── Buffer methods ── */ |
8111 | 1.68k | if (obj.type == VAL_BUFFER) { |
8112 | | /// @method Buffer.len() -> Int |
8113 | | /// @category Buffer Methods |
8114 | | /// Return the number of bytes in the buffer. |
8115 | | /// @example buf.len() |
8116 | 16 | if (strcmp(method, "len") == 0) { |
8117 | 10 | if (arg_count != 0) return eval_err(strdup(".len() takes no arguments")); |
8118 | 10 | return eval_ok(value_int((int64_t)obj.as.buffer.len)); |
8119 | 10 | } |
8120 | | /// @method Buffer.capacity() -> Int |
8121 | | /// @category Buffer Methods |
8122 | | /// Return the current capacity of the buffer. |
8123 | | /// @example buf.capacity() |
8124 | 6 | if (strcmp(method, "capacity") == 0) { |
8125 | 0 | if (arg_count != 0) return eval_err(strdup(".capacity() takes no arguments")); |
8126 | 0 | return eval_ok(value_int((int64_t)obj.as.buffer.cap)); |
8127 | 0 | } |
8128 | | /// @method Buffer.push(byte: Int) -> Unit |
8129 | | /// @category Buffer Methods |
8130 | | /// Append a single byte (0-255) to the buffer. |
8131 | | /// @example buf.push(0x42) |
8132 | 6 | if (strcmp(method, "push") == 0) { |
8133 | 0 | if (arg_count != 1) return eval_err(strdup("Buffer.push() expects 1 argument")); |
8134 | | /* Note: in tree-walker, push on copy doesn't mutate original */ |
8135 | 0 | return eval_ok(value_unit()); |
8136 | 0 | } |
8137 | | /// @method Buffer.push_u16(val: Int) -> Unit |
8138 | | /// @category Buffer Methods |
8139 | | /// Append a 16-bit value as 2 bytes (little-endian). |
8140 | | /// @example buf.push_u16(0x1234) |
8141 | 6 | if (strcmp(method, "push_u16") == 0) { |
8142 | 0 | return eval_ok(value_unit()); |
8143 | 0 | } |
8144 | | /// @method Buffer.push_u32(val: Int) -> Unit |
8145 | | /// @category Buffer Methods |
8146 | | /// Append a 32-bit value as 4 bytes (little-endian). |
8147 | | /// @example buf.push_u32(0x12345678) |
8148 | 6 | if (strcmp(method, "push_u32") == 0) { |
8149 | 0 | return eval_ok(value_unit()); |
8150 | 0 | } |
8151 | | /// @method Buffer.read_u8(idx: Int) -> Int |
8152 | | /// @category Buffer Methods |
8153 | | /// Read a single byte at the given index. |
8154 | | /// @example buf.read_u8(0) |
8155 | 6 | if (strcmp(method, "read_u8") == 0) { |
8156 | 0 | if (arg_count != 1 || args[0].type != VAL_INT) return eval_err(strdup("Buffer.read_u8() expects 1 Int argument")); |
8157 | 0 | size_t i = (size_t)args[0].as.int_val; |
8158 | 0 | if (i >= obj.as.buffer.len) return eval_err(strdup("Buffer.read_u8: index out of bounds")); |
8159 | 0 | return eval_ok(value_int(obj.as.buffer.data[i])); |
8160 | 0 | } |
8161 | | /// @method Buffer.write_u8(idx: Int, val: Int) -> Unit |
8162 | | /// @category Buffer Methods |
8163 | | /// Write a single byte at the given index. |
8164 | | /// @example buf.write_u8(0, 42) |
8165 | 6 | if (strcmp(method, "write_u8") == 0) { |
8166 | 0 | return eval_ok(value_unit()); |
8167 | 0 | } |
8168 | | /// @method Buffer.read_u16(idx: Int) -> Int |
8169 | | /// @category Buffer Methods |
8170 | | /// Read a 16-bit value (little-endian) at the given index. |
8171 | | /// @example buf.read_u16(0) |
8172 | 6 | if (strcmp(method, "read_u16") == 0) { |
8173 | 1 | if (arg_count != 1 || args[0].type != VAL_INT) return eval_err(strdup("Buffer.read_u16() expects 1 Int argument")); |
8174 | 1 | size_t i = (size_t)args[0].as.int_val; |
8175 | 1 | if (i + 2 > obj.as.buffer.len) return eval_err(strdup("Buffer.read_u16: index out of bounds")); |
8176 | 1 | uint16_t v = (uint16_t)(obj.as.buffer.data[i] | (obj.as.buffer.data[i+1] << 8)); |
8177 | 1 | return eval_ok(value_int(v)); |
8178 | 1 | } |
8179 | | /// @method Buffer.write_u16(idx: Int, val: Int) -> Unit |
8180 | | /// @category Buffer Methods |
8181 | | /// Write a 16-bit value (little-endian) at the given index. |
8182 | | /// @example buf.write_u16(0, 0x1234) |
8183 | 5 | if (strcmp(method, "write_u16") == 0) { |
8184 | 0 | return eval_ok(value_unit()); |
8185 | 0 | } |
8186 | | /// @method Buffer.read_u32(idx: Int) -> Int |
8187 | | /// @category Buffer Methods |
8188 | | /// Read a 32-bit value (little-endian) at the given index. |
8189 | | /// @example buf.read_u32(0) |
8190 | 5 | if (strcmp(method, "read_u32") == 0) { |
8191 | 1 | if (arg_count != 1 || args[0].type != VAL_INT) return eval_err(strdup("Buffer.read_u32() expects 1 Int argument")); |
8192 | 1 | size_t i = (size_t)args[0].as.int_val; |
8193 | 1 | if (i + 4 > obj.as.buffer.len) return eval_err(strdup("Buffer.read_u32: index out of bounds")); |
8194 | 1 | uint32_t v = (uint32_t)obj.as.buffer.data[i] |
8195 | 1 | | ((uint32_t)obj.as.buffer.data[i+1] << 8) |
8196 | 1 | | ((uint32_t)obj.as.buffer.data[i+2] << 16) |
8197 | 1 | | ((uint32_t)obj.as.buffer.data[i+3] << 24); |
8198 | 1 | return eval_ok(value_int((int64_t)v)); |
8199 | 1 | } |
8200 | | /// @method Buffer.write_u32(idx: Int, val: Int) -> Unit |
8201 | | /// @category Buffer Methods |
8202 | | /// Write a 32-bit value (little-endian) at the given index. |
8203 | | /// @example buf.write_u32(0, 0x12345678) |
8204 | 4 | if (strcmp(method, "write_u32") == 0) { |
8205 | 0 | return eval_ok(value_unit()); |
8206 | 0 | } |
8207 | | /// @method Buffer.slice(start: Int, end: Int) -> Buffer |
8208 | | /// @category Buffer Methods |
8209 | | /// Return a new buffer containing bytes from start (inclusive) to end (exclusive). |
8210 | | /// @example buf.slice(0, 4) |
8211 | 4 | if (strcmp(method, "slice") == 0) { |
8212 | 1 | if (arg_count != 2) return eval_err(strdup("Buffer.slice() expects 2 arguments")); |
8213 | 1 | if (args[0].type != VAL_INT || args[1].type != VAL_INT) return eval_err(strdup("Buffer.slice() expects Int arguments")); |
8214 | 1 | int64_t s = args[0].as.int_val, e = args[1].as.int_val; |
8215 | 1 | if (s < 0) s = 0; |
8216 | 1 | if (e > (int64_t)obj.as.buffer.len) e = (int64_t)obj.as.buffer.len; |
8217 | 1 | if (s >= e) return eval_ok(value_buffer(NULL, 0)); |
8218 | 1 | return eval_ok(value_buffer(obj.as.buffer.data + s, (size_t)(e - s))); |
8219 | 1 | } |
8220 | | /// @method Buffer.clear() -> Unit |
8221 | | /// @category Buffer Methods |
8222 | | /// Set the buffer length to 0 (capacity unchanged). |
8223 | | /// @example buf.clear() |
8224 | 3 | if (strcmp(method, "clear") == 0) { |
8225 | 0 | return eval_ok(value_unit()); |
8226 | 0 | } |
8227 | | /// @method Buffer.fill(byte: Int) -> Unit |
8228 | | /// @category Buffer Methods |
8229 | | /// Fill all bytes in the buffer with the given value. |
8230 | | /// @example buf.fill(0) |
8231 | 3 | if (strcmp(method, "fill") == 0) { |
8232 | 0 | return eval_ok(value_unit()); |
8233 | 0 | } |
8234 | | /// @method Buffer.resize(new_len: Int) -> Unit |
8235 | | /// @category Buffer Methods |
8236 | | /// Change the buffer length. New bytes are zero-filled. |
8237 | | /// @example buf.resize(32) |
8238 | 3 | if (strcmp(method, "resize") == 0) { |
8239 | 0 | return eval_ok(value_unit()); |
8240 | 0 | } |
8241 | | /// @method Buffer.to_string() -> String |
8242 | | /// @category Buffer Methods |
8243 | | /// Interpret the buffer contents as a UTF-8 string. |
8244 | | /// @example Buffer::from_string("hi").to_string() // "hi" |
8245 | 3 | if (strcmp(method, "to_string") == 0) { |
8246 | 1 | if (arg_count != 0) return eval_err(strdup(".to_string() takes no arguments")); |
8247 | 1 | char *s = malloc(obj.as.buffer.len + 1); |
8248 | 1 | memcpy(s, obj.as.buffer.data, obj.as.buffer.len); |
8249 | 1 | s[obj.as.buffer.len] = '\0'; |
8250 | 1 | return eval_ok(value_string_owned(s)); |
8251 | 1 | } |
8252 | | /// @method Buffer.to_array() -> Array |
8253 | | /// @category Buffer Methods |
8254 | | /// Convert the buffer to an array of integers (0-255). |
8255 | | /// @example buf.to_array() |
8256 | 2 | if (strcmp(method, "to_array") == 0) { |
8257 | 1 | if (arg_count != 0) return eval_err(strdup(".to_array() takes no arguments")); |
8258 | 1 | size_t blen = obj.as.buffer.len; |
8259 | 1 | LatValue *elems = malloc((blen > 0 ? blen : 1) * sizeof(LatValue)); |
8260 | 4 | for (size_t i = 0; i < blen; i++) |
8261 | 3 | elems[i] = value_int(obj.as.buffer.data[i]); |
8262 | 1 | LatValue arr = value_array(elems, blen); |
8263 | 1 | free(elems); |
8264 | 1 | return eval_ok(arr); |
8265 | 1 | } |
8266 | | /// @method Buffer.to_hex() -> String |
8267 | | /// @category Buffer Methods |
8268 | | /// Convert the buffer contents to a hexadecimal string. |
8269 | | /// @example Buffer::from([0x48, 0x69]).to_hex() // "4869" |
8270 | 1 | if (strcmp(method, "to_hex") == 0) { |
8271 | 1 | if (arg_count != 0) return eval_err(strdup(".to_hex() takes no arguments")); |
8272 | 1 | size_t blen = obj.as.buffer.len; |
8273 | 1 | char *hex = malloc(blen * 2 + 1); |
8274 | 4 | for (size_t i = 0; i < blen; i++) |
8275 | 3 | snprintf(hex + i * 2, 3, "%02x", obj.as.buffer.data[i]); |
8276 | 1 | hex[blen * 2] = '\0'; |
8277 | 1 | return eval_ok(value_string_owned(hex)); |
8278 | 1 | } |
8279 | 0 | char *berr2 = NULL; |
8280 | 0 | (void)asprintf(&berr2, "Buffer has no method '%s'", method); |
8281 | 0 | return eval_err(berr2); |
8282 | 1 | } |
8283 | | |
8284 | | /// @method Array.push(val: Any) -> Unit |
8285 | | /// @category Array Methods |
8286 | | /// Append a value to the end of the array (mutates in place). |
8287 | | /// @example arr.push(42) |
8288 | 1.66k | if (strcmp(method, "push") == 0) { |
8289 | 0 | if (obj.type != VAL_ARRAY) |
8290 | 0 | return eval_err(strdup(".push() is not defined on non-array")); |
8291 | 0 | if (value_is_crystal(&obj)) |
8292 | 0 | return eval_err(strdup("cannot push to a crystal array")); |
8293 | 0 | if (arg_count != 1) |
8294 | 0 | return eval_err(strdup(".push() expects exactly 1 argument")); |
8295 | | /* We need to push to the actual array in the env. The obj here is already freed copy. |
8296 | | For method calls that mutate, we'd need the original. Instead, let's not support |
8297 | | standalone push -- in the Rust version it uses Rc<RefCell> for shared mutation. |
8298 | | For C, we'll handle array.push by finding the original binding. */ |
8299 | | /* This is a simplification - we handle push on the env directly */ |
8300 | 0 | return eval_ok(value_unit()); |
8301 | 0 | } |
8302 | | /// @method Array.len() -> Int |
8303 | | /// @method String.len() -> Int |
8304 | | /// @method Map.len() -> Int |
8305 | | /// @category Array Methods |
8306 | | /// Return the number of elements or characters. |
8307 | | /// @example [1, 2, 3].len() // 3 |
8308 | 1.66k | if (strcmp(method, "len") == 0) { |
8309 | 41 | if (obj.type == VAL_ARRAY) return eval_ok(value_int((int64_t)obj.as.array.len)); |
8310 | 20 | if (obj.type == VAL_STR) return eval_ok(value_int((int64_t)strlen(obj.as.str_val))); |
8311 | 11 | if (obj.type == VAL_MAP) return eval_ok(value_int((int64_t)lat_map_len(obj.as.map.map))); |
8312 | 5 | if (obj.type == VAL_TUPLE) return eval_ok(value_int((int64_t)obj.as.tuple.len)); |
8313 | 4 | if (obj.type == VAL_BUFFER) return eval_ok(value_int((int64_t)obj.as.buffer.len)); |
8314 | 4 | if (obj.type == VAL_REF) { |
8315 | 3 | LatValue *inner = &obj.as.ref.ref->value; |
8316 | 3 | if (inner->type == VAL_ARRAY) return eval_ok(value_int((int64_t)inner->as.array.len)); |
8317 | 1 | if (inner->type == VAL_STR) return eval_ok(value_int((int64_t)strlen(inner->as.str_val))); |
8318 | 1 | if (inner->type == VAL_MAP) return eval_ok(value_int((int64_t)lat_map_len(inner->as.map.map))); |
8319 | 0 | if (inner->type == VAL_BUFFER) return eval_ok(value_int((int64_t)inner->as.buffer.len)); |
8320 | 0 | } |
8321 | 1 | return eval_err(strdup(".len() is not defined on this type")); |
8322 | 4 | } |
8323 | | /// @method Array.map(fn: Closure) -> Array |
8324 | | /// @category Array Methods |
8325 | | /// Apply a function to each element, returning a new array of results. |
8326 | | /// @example [1, 2, 3].map(|x| { x * 2 }) // [2, 4, 6] |
8327 | 1.62k | if (strcmp(method, "map") == 0 && obj.type == VAL_ARRAY) { |
8328 | 7 | if (arg_count != 1) return eval_err(strdup(".map() expects exactly 1 argument (a closure)")); |
8329 | 7 | if (args[0].type != VAL_CLOSURE) return eval_err(strdup(".map() argument must be a closure")); |
8330 | | |
8331 | 7 | size_t n = obj.as.array.len; |
8332 | 7 | LatValue *results = malloc(n * sizeof(LatValue)); |
8333 | 139 | for (size_t i = 0; i < n; i++) { |
8334 | 132 | LatValue elem = value_deep_clone(&obj.as.array.elems[i]); |
8335 | 132 | EvalResult r = call_closure(ev, |
8336 | 132 | args[0].as.closure.param_names, |
8337 | 132 | args[0].as.closure.param_count, |
8338 | 132 | args[0].as.closure.body, |
8339 | 132 | args[0].as.closure.captured_env, |
8340 | 132 | &elem, 1, |
8341 | 132 | args[0].as.closure.default_values, args[0].as.closure.has_variadic); |
8342 | 132 | if (!IS_OK(r)) { |
8343 | 0 | GC_POP_N(ev, i); /* accumulated results */ |
8344 | 0 | for (size_t j = 0; j < i; j++) value_free(&results[j]); |
8345 | 0 | free(results); |
8346 | 0 | return r; |
8347 | 0 | } |
8348 | 132 | results[i] = r.value; |
8349 | 132 | GC_PUSH(ev, &results[i]); |
8350 | 132 | } |
8351 | 7 | GC_POP_N(ev, n); /* accumulated results */ |
8352 | 7 | LatValue arr = value_array(results, n); |
8353 | 7 | free(results); |
8354 | 7 | return eval_ok(arr); |
8355 | 7 | } |
8356 | | /// @method Array.join(sep?: String) -> String |
8357 | | /// @category Array Methods |
8358 | | /// Join array elements into a string with an optional separator. |
8359 | | /// @example ["a", "b", "c"].join(", ") // "a, b, c" |
8360 | 1.61k | if (strcmp(method, "join") == 0) { |
8361 | 1 | if (obj.type != VAL_ARRAY) return eval_err(strdup(".join() is not defined on non-array")); |
8362 | 1 | const char *sep = ""; |
8363 | 1 | if (arg_count > 0) { |
8364 | 1 | if (args[0].type != VAL_STR) return eval_err(strdup(".join() separator must be a string")); |
8365 | 1 | sep = args[0].as.str_val; |
8366 | 1 | } |
8367 | 1 | size_t total = 0; |
8368 | 1 | char **parts = malloc(obj.as.array.len * sizeof(char *)); |
8369 | 5 | for (size_t i = 0; i < obj.as.array.len; i++) { |
8370 | 4 | parts[i] = value_display(&obj.as.array.elems[i]); |
8371 | 4 | total += strlen(parts[i]); |
8372 | 4 | } |
8373 | 1 | size_t sep_len = strlen(sep); |
8374 | 1 | if (obj.as.array.len > 0) total += sep_len * (obj.as.array.len - 1); |
8375 | 1 | char *result = malloc(total + 1); |
8376 | 1 | size_t pos = 0; |
8377 | 5 | for (size_t i = 0; i < obj.as.array.len; i++) { |
8378 | 4 | if (i > 0) { memcpy(result + pos, sep, sep_len); pos += sep_len; } |
8379 | 4 | size_t pl = strlen(parts[i]); |
8380 | 4 | memcpy(result + pos, parts[i], pl); |
8381 | 4 | pos += pl; |
8382 | 4 | free(parts[i]); |
8383 | 4 | } |
8384 | 1 | result[pos] = '\0'; |
8385 | 1 | free(parts); |
8386 | 1 | return eval_ok(value_string_owned(result)); |
8387 | 1 | } |
8388 | | /// @method Array.filter(fn: Closure) -> Array |
8389 | | /// @category Array Methods |
8390 | | /// Return a new array containing only elements for which fn returns true. |
8391 | | /// @example [1, 2, 3, 4].filter(|x| { x > 2 }) // [3, 4] |
8392 | | /* ── Array: filter ── */ |
8393 | 1.61k | if (strcmp(method, "filter") == 0 && obj.type == VAL_ARRAY) { |
8394 | 3 | if (arg_count != 1 || args[0].type != VAL_CLOSURE) return eval_err(strdup(".filter() expects 1 closure argument")); |
8395 | 3 | size_t n = obj.as.array.len; |
8396 | 3 | LatValue *results = malloc((n > 0 ? n : 1) * sizeof(LatValue)); |
8397 | 3 | size_t rcount = 0; |
8398 | 75 | for (size_t i = 0; i < n; i++) { |
8399 | 72 | LatValue elem = value_deep_clone(&obj.as.array.elems[i]); |
8400 | 72 | EvalResult r = call_closure(ev, |
8401 | 72 | args[0].as.closure.param_names, |
8402 | 72 | args[0].as.closure.param_count, |
8403 | 72 | args[0].as.closure.body, |
8404 | 72 | args[0].as.closure.captured_env, |
8405 | 72 | &elem, 1, |
8406 | 72 | args[0].as.closure.default_values, args[0].as.closure.has_variadic); |
8407 | 72 | if (!IS_OK(r)) { |
8408 | 0 | GC_POP_N(ev, rcount); /* accumulated results */ |
8409 | 0 | for (size_t j = 0; j < rcount; j++) value_free(&results[j]); |
8410 | 0 | free(results); |
8411 | 0 | return r; |
8412 | 0 | } |
8413 | 72 | if (value_is_truthy(&r.value)) { |
8414 | 33 | results[rcount++] = value_deep_clone(&obj.as.array.elems[i]); |
8415 | 33 | GC_PUSH(ev, &results[rcount - 1]); |
8416 | 33 | } |
8417 | 72 | value_free(&r.value); |
8418 | 72 | } |
8419 | 3 | GC_POP_N(ev, rcount); /* accumulated results */ |
8420 | 3 | LatValue arr = value_array(results, rcount); |
8421 | 3 | free(results); |
8422 | 3 | return eval_ok(arr); |
8423 | 3 | } |
8424 | | /// @method Array.for_each(fn: Closure) -> Unit |
8425 | | /// @category Array Methods |
8426 | | /// Call a function for each element (for side effects). |
8427 | | /// @example [1, 2, 3].for_each(|x| { print(x) }) |
8428 | | /* ── Array: for_each ── */ |
8429 | 1.61k | if (strcmp(method, "for_each") == 0 && obj.type == VAL_ARRAY) { |
8430 | 1 | if (arg_count != 1 || args[0].type != VAL_CLOSURE) return eval_err(strdup(".for_each() expects 1 closure argument")); |
8431 | 4 | for (size_t i = 0; i < obj.as.array.len; i++) { |
8432 | 3 | LatValue elem = value_deep_clone(&obj.as.array.elems[i]); |
8433 | 3 | EvalResult r = call_closure(ev, |
8434 | 3 | args[0].as.closure.param_names, |
8435 | 3 | args[0].as.closure.param_count, |
8436 | 3 | args[0].as.closure.body, |
8437 | 3 | args[0].as.closure.captured_env, |
8438 | 3 | &elem, 1, |
8439 | 3 | args[0].as.closure.default_values, args[0].as.closure.has_variadic); |
8440 | 3 | if (!IS_OK(r)) return r; |
8441 | 3 | value_free(&r.value); |
8442 | 3 | } |
8443 | 1 | return eval_ok(value_unit()); |
8444 | 1 | } |
8445 | | /// @method Array.find(fn: Closure) -> Any|Unit |
8446 | | /// @category Array Methods |
8447 | | /// Return the first element for which fn returns true, or unit if not found. |
8448 | | /// @example [1, 2, 3].find(|x| { x > 1 }) // 2 |
8449 | | /* ── Array: find ── */ |
8450 | 1.61k | if (strcmp(method, "find") == 0) { |
8451 | 2 | if (obj.type != VAL_ARRAY) return eval_err(strdup(".find() is not defined on non-array")); |
8452 | 2 | if (arg_count != 1 || args[0].type != VAL_CLOSURE) return eval_err(strdup(".find() expects 1 closure argument")); |
8453 | 8 | for (size_t i = 0; i < obj.as.array.len; i++) { |
8454 | 7 | LatValue elem = value_deep_clone(&obj.as.array.elems[i]); |
8455 | 7 | EvalResult r = call_closure(ev, |
8456 | 7 | args[0].as.closure.param_names, |
8457 | 7 | args[0].as.closure.param_count, |
8458 | 7 | args[0].as.closure.body, |
8459 | 7 | args[0].as.closure.captured_env, |
8460 | 7 | &elem, 1, |
8461 | 7 | args[0].as.closure.default_values, args[0].as.closure.has_variadic); |
8462 | 7 | if (!IS_OK(r)) return r; |
8463 | 7 | if (value_is_truthy(&r.value)) { |
8464 | 1 | value_free(&r.value); |
8465 | 1 | return eval_ok(value_deep_clone(&obj.as.array.elems[i])); |
8466 | 1 | } |
8467 | 6 | value_free(&r.value); |
8468 | 6 | } |
8469 | 1 | return eval_ok(value_unit()); |
8470 | 2 | } |
8471 | | /// @method Array.contains(val: Any) -> Bool |
8472 | | /// @category Array Methods |
8473 | | /// Check if the array contains a value. |
8474 | | /// @example [1, 2, 3].contains(2) // true |
8475 | | /* ── Array: contains ── */ |
8476 | 1.60k | if (strcmp(method, "contains") == 0 && obj.type == VAL_ARRAY) { |
8477 | 8 | if (arg_count != 1) return eval_err(strdup(".contains() expects 1 argument")); |
8478 | 2.61k | for (size_t i = 0; i < obj.as.array.len; i++) { |
8479 | 2.61k | if (value_eq(&obj.as.array.elems[i], &args[0])) { |
8480 | 6 | return eval_ok(value_bool(true)); |
8481 | 6 | } |
8482 | 2.61k | } |
8483 | 2 | return eval_ok(value_bool(false)); |
8484 | 8 | } |
8485 | | /// @method Array.reverse() -> Array |
8486 | | /// @category Array Methods |
8487 | | /// Return a new array with elements in reverse order. |
8488 | | /// @example [1, 2, 3].reverse() // [3, 2, 1] |
8489 | | /* ── Array: reverse ── */ |
8490 | 1.60k | if (strcmp(method, "reverse") == 0 && obj.type == VAL_ARRAY) { |
8491 | 1 | size_t n = obj.as.array.len; |
8492 | 1 | LatValue *reversed = malloc((n > 0 ? n : 1) * sizeof(LatValue)); |
8493 | 4 | for (size_t i = 0; i < n; i++) { |
8494 | 3 | reversed[i] = value_deep_clone(&obj.as.array.elems[n - 1 - i]); |
8495 | 3 | } |
8496 | 1 | LatValue arr = value_array(reversed, n); |
8497 | 1 | free(reversed); |
8498 | 1 | return eval_ok(arr); |
8499 | 1 | } |
8500 | | /// @method Array.enumerate() -> Array |
8501 | | /// @category Array Methods |
8502 | | /// Return an array of [index, value] pairs. |
8503 | | /// @example ["a", "b"].enumerate() // [[0, "a"], [1, "b"]] |
8504 | | /* ── Array: enumerate ── */ |
8505 | 1.60k | if (strcmp(method, "enumerate") == 0) { |
8506 | 1 | if (obj.type != VAL_ARRAY) return eval_err(strdup(".enumerate() is not defined on non-array")); |
8507 | 1 | size_t n = obj.as.array.len; |
8508 | 1 | LatValue *pairs = malloc((n > 0 ? n : 1) * sizeof(LatValue)); |
8509 | 4 | for (size_t i = 0; i < n; i++) { |
8510 | 3 | LatValue pair_elems[2]; |
8511 | 3 | pair_elems[0] = value_int((int64_t)i); |
8512 | 3 | pair_elems[1] = value_deep_clone(&obj.as.array.elems[i]); |
8513 | 3 | pairs[i] = value_array(pair_elems, 2); |
8514 | 3 | } |
8515 | 1 | LatValue arr = value_array(pairs, n); |
8516 | 1 | free(pairs); |
8517 | 1 | return eval_ok(arr); |
8518 | 1 | } |
8519 | | /// @method Array.sort() -> Array |
8520 | | /// @category Array Methods |
8521 | | /// Return a new sorted array (elements must be comparable). |
8522 | | /// @example [3, 1, 2].sort() // [1, 2, 3] |
8523 | | /* ── Array: sort ── */ |
8524 | 1.59k | if (strcmp(method, "sort") == 0 && obj.type == VAL_ARRAY) { |
8525 | 5 | if (arg_count != 0) return eval_err(strdup(".sort() takes no arguments")); |
8526 | 5 | char *sort_err = NULL; |
8527 | 5 | LatValue sorted = array_sort(&obj, &sort_err); |
8528 | 5 | if (sort_err) return eval_err(sort_err); |
8529 | 4 | return eval_ok(sorted); |
8530 | 5 | } |
8531 | | /// @method Array.flat() -> Array |
8532 | | /// @category Array Methods |
8533 | | /// Flatten one level of nested arrays. |
8534 | | /// @example [[1, 2], [3, 4]].flat() // [1, 2, 3, 4] |
8535 | | /* ── Array: flat ── */ |
8536 | 1.59k | if (strcmp(method, "flat") == 0 && obj.type == VAL_ARRAY) { |
8537 | 4 | if (arg_count != 0) return eval_err(strdup(".flat() takes no arguments")); |
8538 | 4 | return eval_ok(array_flat(&obj)); |
8539 | 4 | } |
8540 | | /// @method Array.reduce(fn: Closure, init: Any) -> Any |
8541 | | /// @category Array Methods |
8542 | | /// Reduce an array to a single value by applying fn(acc, elem) for each element. |
8543 | | /// @example [1, 2, 3].reduce(|a, b| { a + b }, 0) // 6 |
8544 | | /* ── Array: reduce ── */ |
8545 | 1.59k | if (strcmp(method, "reduce") == 0 && obj.type == VAL_ARRAY) { |
8546 | 4 | if (arg_count != 2) return eval_err(strdup(".reduce() expects 2 arguments (closure, initial_value)")); |
8547 | 4 | if (args[0].type != VAL_CLOSURE) return eval_err(strdup(".reduce() first argument must be a closure")); |
8548 | 4 | LatValue acc = value_deep_clone(&args[1]); |
8549 | 4 | GC_PUSH(ev, &acc); |
8550 | 14 | for (size_t i = 0; i < obj.as.array.len; i++) { |
8551 | 10 | LatValue call_args[2]; |
8552 | 10 | call_args[0] = acc; |
8553 | 10 | call_args[1] = value_deep_clone(&obj.as.array.elems[i]); |
8554 | 10 | EvalResult r = call_closure(ev, |
8555 | 10 | args[0].as.closure.param_names, |
8556 | 10 | args[0].as.closure.param_count, |
8557 | 10 | args[0].as.closure.body, |
8558 | 10 | args[0].as.closure.captured_env, |
8559 | 10 | call_args, 2, |
8560 | 10 | args[0].as.closure.default_values, args[0].as.closure.has_variadic); |
8561 | 10 | if (!IS_OK(r)) { GC_POP(ev); return r; } |
8562 | 10 | acc = r.value; |
8563 | 10 | } |
8564 | 4 | GC_POP(ev); |
8565 | 4 | return eval_ok(acc); |
8566 | 4 | } |
8567 | | /// @method Array.slice(start: Int, end: Int) -> Array |
8568 | | /// @category Array Methods |
8569 | | /// Return a sub-array from start (inclusive) to end (exclusive). |
8570 | | /// @example [1, 2, 3, 4, 5].slice(1, 4) // [2, 3, 4] |
8571 | | /* ── Array: slice ── */ |
8572 | 1.58k | if (strcmp(method, "slice") == 0 && obj.type == VAL_ARRAY) { |
8573 | 4 | if (arg_count != 2) return eval_err(strdup(".slice() expects 2 arguments (start, end)")); |
8574 | 4 | if (args[0].type != VAL_INT || args[1].type != VAL_INT) |
8575 | 0 | return eval_err(strdup(".slice() arguments must be integers")); |
8576 | 4 | char *slice_err = NULL; |
8577 | 4 | LatValue sliced = array_slice(&obj, args[0].as.int_val, args[1].as.int_val, &slice_err); |
8578 | 4 | if (slice_err) return eval_err(slice_err); |
8579 | 4 | return eval_ok(sliced); |
8580 | 4 | } |
8581 | | /// @method Array.take(n: Int) -> Array |
8582 | | /// @category Array Methods |
8583 | | /// Return the first n elements of the array. |
8584 | | /// @example [1, 2, 3, 4].take(2) // [1, 2] |
8585 | | /* ── Array: take ── */ |
8586 | 1.58k | if (strcmp(method, "take") == 0 && obj.type == VAL_ARRAY) { |
8587 | 3 | if (arg_count != 1 || args[0].type != VAL_INT) |
8588 | 0 | return eval_err(strdup(".take() expects 1 integer argument")); |
8589 | 3 | int64_t n = args[0].as.int_val; |
8590 | 3 | if (n <= 0) { |
8591 | 1 | return eval_ok(value_array(NULL, 0)); |
8592 | 1 | } |
8593 | 2 | size_t take_count = (size_t)n; |
8594 | 2 | if (take_count > obj.as.array.len) take_count = obj.as.array.len; |
8595 | 2 | LatValue *elems = malloc((take_count > 0 ? take_count : 1) * sizeof(LatValue)); |
8596 | 7 | for (size_t i = 0; i < take_count; i++) { |
8597 | 5 | elems[i] = value_deep_clone(&obj.as.array.elems[i]); |
8598 | 5 | } |
8599 | 2 | LatValue arr = value_array(elems, take_count); |
8600 | 2 | free(elems); |
8601 | 2 | return eval_ok(arr); |
8602 | 3 | } |
8603 | | /// @method Array.drop(n: Int) -> Array |
8604 | | /// @category Array Methods |
8605 | | /// Return the array with the first n elements removed. |
8606 | | /// @example [1, 2, 3, 4].drop(2) // [3, 4] |
8607 | | /* ── Array: drop ── */ |
8608 | 1.57k | if (strcmp(method, "drop") == 0 && obj.type == VAL_ARRAY) { |
8609 | 3 | if (arg_count != 1 || args[0].type != VAL_INT) |
8610 | 0 | return eval_err(strdup(".drop() expects 1 integer argument")); |
8611 | 3 | int64_t n = args[0].as.int_val; |
8612 | 3 | if (n <= 0) { |
8613 | 1 | LatValue *elems = malloc((obj.as.array.len > 0 ? obj.as.array.len : 1) * sizeof(LatValue)); |
8614 | 4 | for (size_t i = 0; i < obj.as.array.len; i++) { |
8615 | 3 | elems[i] = value_deep_clone(&obj.as.array.elems[i]); |
8616 | 3 | } |
8617 | 1 | LatValue arr = value_array(elems, obj.as.array.len); |
8618 | 1 | free(elems); |
8619 | 1 | return eval_ok(arr); |
8620 | 1 | } |
8621 | 2 | size_t start = (size_t)n; |
8622 | 2 | if (start >= obj.as.array.len) { |
8623 | 1 | return eval_ok(value_array(NULL, 0)); |
8624 | 1 | } |
8625 | 1 | size_t drop_count = obj.as.array.len - start; |
8626 | 1 | LatValue *elems = malloc(drop_count * sizeof(LatValue)); |
8627 | 4 | for (size_t i = 0; i < drop_count; i++) { |
8628 | 3 | elems[i] = value_deep_clone(&obj.as.array.elems[start + i]); |
8629 | 3 | } |
8630 | 1 | LatValue arr = value_array(elems, drop_count); |
8631 | 1 | free(elems); |
8632 | 1 | return eval_ok(arr); |
8633 | 2 | } |
8634 | | /// @method Array.pop() -> Any |
8635 | | /// @category Array Methods |
8636 | | /// Remove and return the last element of the array. |
8637 | | /// @example [1, 2, 3].pop() // 3 |
8638 | | /* ── Array: pop ── */ |
8639 | 1.57k | if (strcmp(method, "pop") == 0 && obj.type == VAL_ARRAY) { |
8640 | 0 | if (arg_count != 0) return eval_err(strdup(".pop() takes no arguments")); |
8641 | 0 | if (obj.as.array.len == 0) return eval_err(strdup("pop on empty array")); |
8642 | 0 | LatValue removed = value_deep_clone(&obj.as.array.elems[obj.as.array.len - 1]); |
8643 | 0 | return eval_ok(removed); |
8644 | 0 | } |
8645 | | /// @method Array.index_of(val: Any) -> Int |
8646 | | /// @category Array Methods |
8647 | | /// Return the index of the first occurrence of val, or -1 if not found. |
8648 | | /// @example [10, 20, 30].index_of(20) // 1 |
8649 | | /* ── Array: index_of ── */ |
8650 | 1.57k | if (strcmp(method, "index_of") == 0 && obj.type == VAL_ARRAY) { |
8651 | 2 | if (arg_count != 1) return eval_err(strdup(".index_of() expects 1 argument")); |
8652 | 6 | for (size_t i = 0; i < obj.as.array.len; i++) { |
8653 | 5 | if (value_eq(&obj.as.array.elems[i], &args[0])) { |
8654 | 1 | return eval_ok(value_int((int64_t)i)); |
8655 | 1 | } |
8656 | 5 | } |
8657 | 1 | return eval_ok(value_int(-1)); |
8658 | 2 | } |
8659 | | /// @method Array.any(fn: Closure) -> Bool |
8660 | | /// @category Array Methods |
8661 | | /// Return true if fn returns true for any element. |
8662 | | /// @example [1, 2, 3].any(|x| { x > 2 }) // true |
8663 | | /* ── Array: any ── */ |
8664 | 1.57k | if (strcmp(method, "any") == 0 && obj.type == VAL_ARRAY) { |
8665 | 2 | if (arg_count != 1 || args[0].type != VAL_CLOSURE) |
8666 | 0 | return eval_err(strdup(".any() expects 1 closure argument")); |
8667 | 7 | for (size_t i = 0; i < obj.as.array.len; i++) { |
8668 | 6 | LatValue elem = value_deep_clone(&obj.as.array.elems[i]); |
8669 | 6 | EvalResult r = call_closure(ev, |
8670 | 6 | args[0].as.closure.param_names, |
8671 | 6 | args[0].as.closure.param_count, |
8672 | 6 | args[0].as.closure.body, |
8673 | 6 | args[0].as.closure.captured_env, |
8674 | 6 | &elem, 1, |
8675 | 6 | args[0].as.closure.default_values, args[0].as.closure.has_variadic); |
8676 | 6 | if (!IS_OK(r)) return r; |
8677 | 6 | if (value_is_truthy(&r.value)) { |
8678 | 1 | value_free(&r.value); |
8679 | 1 | return eval_ok(value_bool(true)); |
8680 | 1 | } |
8681 | 5 | value_free(&r.value); |
8682 | 5 | } |
8683 | 1 | return eval_ok(value_bool(false)); |
8684 | 2 | } |
8685 | | /// @method Array.all(fn: Closure) -> Bool |
8686 | | /// @category Array Methods |
8687 | | /// Return true if fn returns true for all elements. |
8688 | | /// @example [2, 4, 6].all(|x| { x % 2 == 0 }) // true |
8689 | | /* ── Array: all ── */ |
8690 | 1.57k | if (strcmp(method, "all") == 0 && obj.type == VAL_ARRAY) { |
8691 | 2 | if (arg_count != 1 || args[0].type != VAL_CLOSURE) |
8692 | 0 | return eval_err(strdup(".all() expects 1 closure argument")); |
8693 | 5 | for (size_t i = 0; i < obj.as.array.len; i++) { |
8694 | 4 | LatValue elem = value_deep_clone(&obj.as.array.elems[i]); |
8695 | 4 | EvalResult r = call_closure(ev, |
8696 | 4 | args[0].as.closure.param_names, |
8697 | 4 | args[0].as.closure.param_count, |
8698 | 4 | args[0].as.closure.body, |
8699 | 4 | args[0].as.closure.captured_env, |
8700 | 4 | &elem, 1, |
8701 | 4 | args[0].as.closure.default_values, args[0].as.closure.has_variadic); |
8702 | 4 | if (!IS_OK(r)) return r; |
8703 | 4 | if (!value_is_truthy(&r.value)) { |
8704 | 1 | value_free(&r.value); |
8705 | 1 | return eval_ok(value_bool(false)); |
8706 | 1 | } |
8707 | 3 | value_free(&r.value); |
8708 | 3 | } |
8709 | 1 | return eval_ok(value_bool(true)); |
8710 | 2 | } |
8711 | | /// @method Array.zip(other: Array) -> Array |
8712 | | /// @category Array Methods |
8713 | | /// Combine two arrays into an array of [a, b] pairs. |
8714 | | /// @example [1, 2].zip(["a", "b"]) // [[1, "a"], [2, "b"]] |
8715 | | /* ── Array: zip ── */ |
8716 | 1.57k | if (strcmp(method, "zip") == 0 && obj.type == VAL_ARRAY) { |
8717 | 1 | if (arg_count != 1) return eval_err(strdup(".zip() expects 1 argument")); |
8718 | 1 | if (args[0].type != VAL_ARRAY) return eval_err(strdup(".zip() argument must be an array")); |
8719 | 1 | size_t n = obj.as.array.len < args[0].as.array.len |
8720 | 1 | ? obj.as.array.len : args[0].as.array.len; |
8721 | 1 | LatValue *pairs = malloc((n > 0 ? n : 1) * sizeof(LatValue)); |
8722 | 3 | for (size_t i = 0; i < n; i++) { |
8723 | 2 | LatValue pair_elems[2]; |
8724 | 2 | pair_elems[0] = value_deep_clone(&obj.as.array.elems[i]); |
8725 | 2 | pair_elems[1] = value_deep_clone(&args[0].as.array.elems[i]); |
8726 | 2 | pairs[i] = value_array(pair_elems, 2); |
8727 | 2 | } |
8728 | 1 | LatValue arr = value_array(pairs, n); |
8729 | 1 | free(pairs); |
8730 | 1 | return eval_ok(arr); |
8731 | 1 | } |
8732 | | /// @method Array.unique() -> Array |
8733 | | /// @category Array Methods |
8734 | | /// Return a new array with duplicate elements removed. |
8735 | | /// @example [1, 2, 2, 3, 1].unique() // [1, 2, 3] |
8736 | | /* ── Array: unique ── */ |
8737 | 1.56k | if (strcmp(method, "unique") == 0 && obj.type == VAL_ARRAY) { |
8738 | 1 | if (arg_count != 0) return eval_err(strdup(".unique() takes no arguments")); |
8739 | 1 | size_t n = obj.as.array.len; |
8740 | 1 | LatValue *results = malloc((n > 0 ? n : 1) * sizeof(LatValue)); |
8741 | 1 | size_t rcount = 0; |
8742 | 7 | for (size_t i = 0; i < n; i++) { |
8743 | 6 | bool found = false; |
8744 | 13 | for (size_t j = 0; j < rcount; j++) { |
8745 | 9 | if (value_eq(&obj.as.array.elems[i], &results[j])) { |
8746 | 2 | found = true; |
8747 | 2 | break; |
8748 | 2 | } |
8749 | 9 | } |
8750 | 6 | if (!found) { |
8751 | 4 | results[rcount++] = value_deep_clone(&obj.as.array.elems[i]); |
8752 | 4 | } |
8753 | 6 | } |
8754 | 1 | LatValue arr = value_array(results, rcount); |
8755 | 1 | free(results); |
8756 | 1 | return eval_ok(arr); |
8757 | 1 | } |
8758 | | /// @method Array.insert(index: Int, val: Any) -> Unit |
8759 | | /// @category Array Methods |
8760 | | /// Insert a value at the given index (mutates in place). |
8761 | | /// @example arr.insert(1, "x") |
8762 | | /* ── Array: insert ── */ |
8763 | 1.56k | if (strcmp(method, "insert") == 0 && obj.type == VAL_ARRAY) { |
8764 | 0 | if (arg_count != 2) return eval_err(strdup(".insert() expects 2 arguments (index, value)")); |
8765 | 0 | if (args[0].type != VAL_INT) return eval_err(strdup(".insert() index must be an integer")); |
8766 | 0 | int64_t idx = args[0].as.int_val; |
8767 | 0 | size_t len = obj.as.array.len; |
8768 | 0 | if (idx < 0 || (size_t)idx > len) { |
8769 | 0 | char *err = NULL; |
8770 | 0 | (void)asprintf(&err, ".insert() index %lld out of bounds (length %zu)", |
8771 | 0 | (long long)idx, len); |
8772 | 0 | return eval_err(err); |
8773 | 0 | } |
8774 | 0 | return eval_ok(value_unit()); |
8775 | 0 | } |
8776 | | /// @method Array.remove_at(index: Int) -> Any |
8777 | | /// @category Array Methods |
8778 | | /// Remove and return the element at the given index. |
8779 | | /// @example [1, 2, 3].remove_at(1) // 2 |
8780 | | /* ── Array: remove_at ── */ |
8781 | 1.56k | if (strcmp(method, "remove_at") == 0 && obj.type == VAL_ARRAY) { |
8782 | 0 | if (arg_count != 1) return eval_err(strdup(".remove_at() expects 1 argument (index)")); |
8783 | 0 | if (args[0].type != VAL_INT) return eval_err(strdup(".remove_at() index must be an integer")); |
8784 | 0 | int64_t idx = args[0].as.int_val; |
8785 | 0 | size_t len = obj.as.array.len; |
8786 | 0 | if (idx < 0 || (size_t)idx >= len) { |
8787 | 0 | char *err = NULL; |
8788 | 0 | (void)asprintf(&err, ".remove_at() index %lld out of bounds (length %zu)", |
8789 | 0 | (long long)idx, len); |
8790 | 0 | return eval_err(err); |
8791 | 0 | } |
8792 | 0 | LatValue removed = value_deep_clone(&obj.as.array.elems[(size_t)idx]); |
8793 | 0 | return eval_ok(removed); |
8794 | 0 | } |
8795 | | /// @method Array.sort_by(cmp: Closure) -> Array |
8796 | | /// @category Array Methods |
8797 | | /// Sort using a custom comparator that returns a negative, zero, or positive Int. |
8798 | | /// @example ["bb", "a", "ccc"].sort_by(|a, b| { len(a) - len(b) }) // ["a", "bb", "ccc"] |
8799 | | /* ── Array: sort_by ── */ |
8800 | 1.56k | if (strcmp(method, "sort_by") == 0 && obj.type == VAL_ARRAY) { |
8801 | 2 | if (arg_count != 1 || args[0].type != VAL_CLOSURE) |
8802 | 0 | return eval_err(strdup(".sort_by() expects 1 closure argument")); |
8803 | 2 | size_t n = obj.as.array.len; |
8804 | | /* Deep-clone elements into a working buffer */ |
8805 | 2 | LatValue *buf = malloc((n > 0 ? n : 1) * sizeof(LatValue)); |
8806 | 12 | for (size_t i = 0; i < n; i++) { |
8807 | 10 | buf[i] = value_deep_clone(&obj.as.array.elems[i]); |
8808 | 10 | } |
8809 | | /* Insertion sort using the closure as comparator */ |
8810 | 10 | for (size_t i = 1; i < n; i++) { |
8811 | 8 | LatValue key = buf[i]; |
8812 | 8 | size_t j = i; |
8813 | 17 | while (j > 0) { |
8814 | 14 | LatValue call_args[2]; |
8815 | 14 | call_args[0] = value_deep_clone(&key); |
8816 | 14 | call_args[1] = value_deep_clone(&buf[j - 1]); |
8817 | 14 | EvalResult r = call_closure(ev, |
8818 | 14 | args[0].as.closure.param_names, |
8819 | 14 | args[0].as.closure.param_count, |
8820 | 14 | args[0].as.closure.body, |
8821 | 14 | args[0].as.closure.captured_env, |
8822 | 14 | call_args, 2, |
8823 | 14 | args[0].as.closure.default_values, args[0].as.closure.has_variadic); |
8824 | 14 | if (!IS_OK(r)) { |
8825 | 0 | for (size_t k = 0; k < j; k++) value_free(&buf[k]); |
8826 | 0 | value_free(&key); |
8827 | 0 | for (size_t k = j + 1; k < n; k++) value_free(&buf[k]); |
8828 | 0 | free(buf); |
8829 | 0 | return r; |
8830 | 0 | } |
8831 | 14 | if (r.value.type != VAL_INT) { |
8832 | 0 | value_free(&r.value); |
8833 | 0 | for (size_t k = 0; k < j; k++) value_free(&buf[k]); |
8834 | 0 | value_free(&key); |
8835 | 0 | for (size_t k = j + 1; k < n; k++) value_free(&buf[k]); |
8836 | 0 | free(buf); |
8837 | 0 | return eval_err(strdup(".sort_by() comparator must return an Int")); |
8838 | 0 | } |
8839 | 14 | int64_t cmp = r.value.as.int_val; |
8840 | 14 | value_free(&r.value); |
8841 | 14 | if (cmp >= 0) break; |
8842 | 9 | buf[j] = buf[j - 1]; |
8843 | 9 | j--; |
8844 | 9 | } |
8845 | 8 | buf[j] = key; |
8846 | 8 | } |
8847 | 2 | LatValue arr = value_array(buf, n); |
8848 | 2 | free(buf); |
8849 | 2 | return eval_ok(arr); |
8850 | 2 | } |
8851 | | /// @method Array.flat_map(fn: Closure) -> Array |
8852 | | /// @category Array Methods |
8853 | | /// Map each element to an array, then flatten one level. |
8854 | | /// @example [1, 2].flat_map(|x| { [x, x * 10] }) // [1, 10, 2, 20] |
8855 | | /* ── Array: flat_map ── */ |
8856 | 1.56k | if (strcmp(method, "flat_map") == 0 && obj.type == VAL_ARRAY) { |
8857 | 3 | if (arg_count != 1 || args[0].type != VAL_CLOSURE) |
8858 | 0 | return eval_err(strdup(".flat_map() expects 1 closure argument")); |
8859 | 3 | size_t n = obj.as.array.len; |
8860 | 3 | LatValue *mapped = malloc((n > 0 ? n : 1) * sizeof(LatValue)); |
8861 | 9 | for (size_t i = 0; i < n; i++) { |
8862 | 6 | LatValue elem = value_deep_clone(&obj.as.array.elems[i]); |
8863 | 6 | EvalResult r = call_closure(ev, |
8864 | 6 | args[0].as.closure.param_names, |
8865 | 6 | args[0].as.closure.param_count, |
8866 | 6 | args[0].as.closure.body, |
8867 | 6 | args[0].as.closure.captured_env, |
8868 | 6 | &elem, 1, |
8869 | 6 | args[0].as.closure.default_values, args[0].as.closure.has_variadic); |
8870 | 6 | if (!IS_OK(r)) { |
8871 | 0 | for (size_t j = 0; j < i; j++) value_free(&mapped[j]); |
8872 | 0 | free(mapped); |
8873 | 0 | return r; |
8874 | 0 | } |
8875 | 6 | mapped[i] = r.value; |
8876 | 6 | } |
8877 | 3 | size_t total = 0; |
8878 | 9 | for (size_t i = 0; i < n; i++) { |
8879 | 6 | if (mapped[i].type == VAL_ARRAY) |
8880 | 3 | total += mapped[i].as.array.len; |
8881 | 3 | else |
8882 | 3 | total += 1; |
8883 | 6 | } |
8884 | 3 | LatValue *fm_buf = malloc((total > 0 ? total : 1) * sizeof(LatValue)); |
8885 | 3 | size_t fm_pos = 0; |
8886 | 9 | for (size_t i = 0; i < n; i++) { |
8887 | 6 | if (mapped[i].type == VAL_ARRAY) { |
8888 | 9 | for (size_t j = 0; j < mapped[i].as.array.len; j++) { |
8889 | 6 | fm_buf[fm_pos++] = value_deep_clone(&mapped[i].as.array.elems[j]); |
8890 | 6 | } |
8891 | 3 | } else { |
8892 | 3 | fm_buf[fm_pos++] = value_deep_clone(&mapped[i]); |
8893 | 3 | } |
8894 | 6 | } |
8895 | 9 | for (size_t i = 0; i < n; i++) value_free(&mapped[i]); |
8896 | 3 | free(mapped); |
8897 | 3 | LatValue fm_arr = value_array(fm_buf, fm_pos); |
8898 | 3 | free(fm_buf); |
8899 | 3 | return eval_ok(fm_arr); |
8900 | 3 | } |
8901 | | /// @method Array.chunk(size: Int) -> Array |
8902 | | /// @category Array Methods |
8903 | | /// Split the array into sub-arrays of the given size. |
8904 | | /// @example [1, 2, 3, 4, 5].chunk(2) // [[1, 2], [3, 4], [5]] |
8905 | | /* ── Array: chunk ── */ |
8906 | 1.56k | if (strcmp(method, "chunk") == 0 && obj.type == VAL_ARRAY) { |
8907 | 5 | if (arg_count != 1 || args[0].type != VAL_INT) |
8908 | 0 | return eval_err(strdup(".chunk() expects 1 integer argument")); |
8909 | 5 | int64_t chunk_size = args[0].as.int_val; |
8910 | 5 | if (chunk_size <= 0) |
8911 | 0 | return eval_err(strdup(".chunk() size must be positive")); |
8912 | 5 | size_t n = obj.as.array.len; |
8913 | 5 | size_t num_chunks = (n > 0) ? (n + (size_t)chunk_size - 1) / (size_t)chunk_size : 0; |
8914 | 5 | LatValue *chunks = malloc((num_chunks > 0 ? num_chunks : 1) * sizeof(LatValue)); |
8915 | 14 | for (size_t ci = 0; ci < num_chunks; ci++) { |
8916 | 9 | size_t cstart = ci * (size_t)chunk_size; |
8917 | 9 | size_t cend = cstart + (size_t)chunk_size; |
8918 | 9 | if (cend > n) cend = n; |
8919 | 9 | size_t clen = cend - cstart; |
8920 | 9 | LatValue *celems = malloc(clen * sizeof(LatValue)); |
8921 | 25 | for (size_t j = 0; j < clen; j++) { |
8922 | 16 | celems[j] = value_deep_clone(&obj.as.array.elems[cstart + j]); |
8923 | 16 | } |
8924 | 9 | chunks[ci] = value_array(celems, clen); |
8925 | 9 | free(celems); |
8926 | 9 | } |
8927 | 5 | LatValue chunk_arr = value_array(chunks, num_chunks); |
8928 | 5 | free(chunks); |
8929 | 5 | return eval_ok(chunk_arr); |
8930 | 5 | } |
8931 | | /// @method Array.group_by(fn: Closure) -> Map |
8932 | | /// @category Array Methods |
8933 | | /// Group elements by the result of fn, returning a map of key to arrays. |
8934 | | /// @example [1, 2, 3, 4].group_by(|x| { x % 2 }) // {0: [2, 4], 1: [1, 3]} |
8935 | | /* ── Array: group_by ── */ |
8936 | 1.55k | if (strcmp(method, "group_by") == 0 && obj.type == VAL_ARRAY) { |
8937 | 1 | if (arg_count != 1 || args[0].type != VAL_CLOSURE) |
8938 | 0 | return eval_err(strdup(".group_by() expects 1 closure argument")); |
8939 | 1 | LatValue grp_map = value_map_new(); |
8940 | 6 | for (size_t i = 0; i < obj.as.array.len; i++) { |
8941 | 5 | LatValue elem = value_deep_clone(&obj.as.array.elems[i]); |
8942 | 5 | EvalResult r = call_closure(ev, |
8943 | 5 | args[0].as.closure.param_names, |
8944 | 5 | args[0].as.closure.param_count, |
8945 | 5 | args[0].as.closure.body, |
8946 | 5 | args[0].as.closure.captured_env, |
8947 | 5 | &elem, 1, |
8948 | 5 | args[0].as.closure.default_values, args[0].as.closure.has_variadic); |
8949 | 5 | if (!IS_OK(r)) { |
8950 | 0 | value_free(&grp_map); |
8951 | 0 | return r; |
8952 | 0 | } |
8953 | 5 | char *grp_key = value_display(&r.value); |
8954 | 5 | value_free(&r.value); |
8955 | 5 | LatValue *existing = (LatValue *)lat_map_get(grp_map.as.map.map, grp_key); |
8956 | 5 | if (existing) { |
8957 | 3 | size_t old_len = existing->as.array.len; |
8958 | 3 | LatValue *new_elems = malloc((old_len + 1) * sizeof(LatValue)); |
8959 | 7 | for (size_t j = 0; j < old_len; j++) { |
8960 | 4 | new_elems[j] = value_deep_clone(&existing->as.array.elems[j]); |
8961 | 4 | } |
8962 | 3 | new_elems[old_len] = value_deep_clone(&obj.as.array.elems[i]); |
8963 | 3 | LatValue new_grp_arr = value_array(new_elems, old_len + 1); |
8964 | 3 | free(new_elems); |
8965 | 3 | lat_map_set(grp_map.as.map.map, grp_key, &new_grp_arr); |
8966 | 3 | } else { |
8967 | 2 | LatValue cloned = value_deep_clone(&obj.as.array.elems[i]); |
8968 | 2 | LatValue new_grp_arr = value_array(&cloned, 1); |
8969 | 2 | lat_map_set(grp_map.as.map.map, grp_key, &new_grp_arr); |
8970 | 2 | } |
8971 | 5 | free(grp_key); |
8972 | 5 | } |
8973 | 1 | return eval_ok(grp_map); |
8974 | 1 | } |
8975 | | /// @method Array.sum() -> Int|Float |
8976 | | /// @category Array Methods |
8977 | | /// Return the sum of all numeric elements. |
8978 | | /// @example [1, 2, 3].sum() // 6 |
8979 | | /* ── Array: sum ── */ |
8980 | 1.55k | if (strcmp(method, "sum") == 0 && obj.type == VAL_ARRAY) { |
8981 | 3 | if (arg_count != 0) return eval_err(strdup(".sum() takes no arguments")); |
8982 | 3 | bool sum_has_float = false; |
8983 | 3 | int64_t isum = 0; |
8984 | 3 | double fsum = 0.0; |
8985 | 11 | for (size_t i = 0; i < obj.as.array.len; i++) { |
8986 | 8 | if (obj.as.array.elems[i].type == VAL_INT) { |
8987 | 5 | isum += obj.as.array.elems[i].as.int_val; |
8988 | 5 | fsum += (double)obj.as.array.elems[i].as.int_val; |
8989 | 5 | } else if (obj.as.array.elems[i].type == VAL_FLOAT) { |
8990 | 3 | sum_has_float = true; |
8991 | 3 | fsum += obj.as.array.elems[i].as.float_val; |
8992 | 3 | } else { |
8993 | 0 | return eval_err(strdup(".sum() requires all elements to be numeric")); |
8994 | 0 | } |
8995 | 8 | } |
8996 | 3 | if (sum_has_float) return eval_ok(value_float(fsum)); |
8997 | 2 | return eval_ok(value_int(isum)); |
8998 | 3 | } |
8999 | | /// @method Array.min() -> Int|Float |
9000 | | /// @category Array Methods |
9001 | | /// Return the minimum element (all elements must be numeric). |
9002 | | /// @example [3, 1, 2].min() // 1 |
9003 | | /* ── Array: min ── */ |
9004 | 1.55k | if (strcmp(method, "min") == 0 && obj.type == VAL_ARRAY) { |
9005 | 3 | if (arg_count != 0) return eval_err(strdup(".min() takes no arguments")); |
9006 | 3 | if (obj.as.array.len == 0) return eval_err(strdup(".min() on empty array")); |
9007 | 3 | bool min_has_float = false; |
9008 | 10 | for (size_t i = 0; i < obj.as.array.len; i++) { |
9009 | 8 | if (obj.as.array.elems[i].type == VAL_FLOAT) min_has_float = true; |
9010 | 5 | else if (obj.as.array.elems[i].type != VAL_INT) |
9011 | 0 | return eval_err(strdup(".min() requires all elements to be numeric")); |
9012 | 8 | } |
9013 | 2 | if (min_has_float) { |
9014 | 1 | double fmin = (obj.as.array.elems[0].type == VAL_FLOAT) |
9015 | 1 | ? obj.as.array.elems[0].as.float_val |
9016 | 1 | : (double)obj.as.array.elems[0].as.int_val; |
9017 | 3 | for (size_t i = 1; i < obj.as.array.len; i++) { |
9018 | 2 | double v = (obj.as.array.elems[i].type == VAL_FLOAT) |
9019 | 2 | ? obj.as.array.elems[i].as.float_val |
9020 | 2 | : (double)obj.as.array.elems[i].as.int_val; |
9021 | 2 | if (v < fmin) fmin = v; |
9022 | 2 | } |
9023 | 1 | return eval_ok(value_float(fmin)); |
9024 | 1 | } |
9025 | 1 | int64_t imin = obj.as.array.elems[0].as.int_val; |
9026 | 5 | for (size_t i = 1; i < obj.as.array.len; i++) { |
9027 | 4 | if (obj.as.array.elems[i].as.int_val < imin) |
9028 | 1 | imin = obj.as.array.elems[i].as.int_val; |
9029 | 4 | } |
9030 | 1 | return eval_ok(value_int(imin)); |
9031 | 2 | } |
9032 | | /// @method Array.max() -> Int|Float |
9033 | | /// @category Array Methods |
9034 | | /// Return the maximum element (all elements must be numeric). |
9035 | | /// @example [3, 1, 2].max() // 3 |
9036 | | /* ── Array: max ── */ |
9037 | 1.55k | if (strcmp(method, "max") == 0 && obj.type == VAL_ARRAY) { |
9038 | 3 | if (arg_count != 0) return eval_err(strdup(".max() takes no arguments")); |
9039 | 3 | if (obj.as.array.len == 0) return eval_err(strdup(".max() on empty array")); |
9040 | 3 | bool max_has_float = false; |
9041 | 10 | for (size_t i = 0; i < obj.as.array.len; i++) { |
9042 | 8 | if (obj.as.array.elems[i].type == VAL_FLOAT) max_has_float = true; |
9043 | 5 | else if (obj.as.array.elems[i].type != VAL_INT) |
9044 | 0 | return eval_err(strdup(".max() requires all elements to be numeric")); |
9045 | 8 | } |
9046 | 2 | if (max_has_float) { |
9047 | 1 | double fmax = (obj.as.array.elems[0].type == VAL_FLOAT) |
9048 | 1 | ? obj.as.array.elems[0].as.float_val |
9049 | 1 | : (double)obj.as.array.elems[0].as.int_val; |
9050 | 3 | for (size_t i = 1; i < obj.as.array.len; i++) { |
9051 | 2 | double v = (obj.as.array.elems[i].type == VAL_FLOAT) |
9052 | 2 | ? obj.as.array.elems[i].as.float_val |
9053 | 2 | : (double)obj.as.array.elems[i].as.int_val; |
9054 | 2 | if (v > fmax) fmax = v; |
9055 | 2 | } |
9056 | 1 | return eval_ok(value_float(fmax)); |
9057 | 1 | } |
9058 | 1 | int64_t imax = obj.as.array.elems[0].as.int_val; |
9059 | 5 | for (size_t i = 1; i < obj.as.array.len; i++) { |
9060 | 4 | if (obj.as.array.elems[i].as.int_val > imax) |
9061 | 2 | imax = obj.as.array.elems[i].as.int_val; |
9062 | 4 | } |
9063 | 1 | return eval_ok(value_int(imax)); |
9064 | 2 | } |
9065 | | /// @method Array.first() -> Any|Unit |
9066 | | /// @category Array Methods |
9067 | | /// Return the first element, or unit if the array is empty. |
9068 | | /// @example [1, 2, 3].first() // 1 |
9069 | | /* ── Array: first ── */ |
9070 | 1.54k | if (strcmp(method, "first") == 0 && obj.type == VAL_ARRAY) { |
9071 | 9 | if (arg_count != 0) return eval_err(strdup(".first() takes no arguments")); |
9072 | 9 | if (obj.as.array.len == 0) return eval_ok(value_unit()); |
9073 | 8 | return eval_ok(value_deep_clone(&obj.as.array.elems[0])); |
9074 | 9 | } |
9075 | | /// @method Array.last() -> Any|Unit |
9076 | | /// @category Array Methods |
9077 | | /// Return the last element, or unit if the array is empty. |
9078 | | /// @example [1, 2, 3].last() // 3 |
9079 | | /* ── Array: last ── */ |
9080 | 1.53k | if (strcmp(method, "last") == 0 && obj.type == VAL_ARRAY) { |
9081 | 3 | if (arg_count != 0) return eval_err(strdup(".last() takes no arguments")); |
9082 | 3 | if (obj.as.array.len == 0) return eval_ok(value_unit()); |
9083 | 2 | return eval_ok(value_deep_clone(&obj.as.array.elems[obj.as.array.len - 1])); |
9084 | 3 | } |
9085 | | /* ── Map methods ── */ |
9086 | 1.53k | if (obj.type == VAL_MAP) { |
9087 | | /// @method Map.get(key: String) -> Any|Unit |
9088 | | /// @category Map Methods |
9089 | | /// Get the value for a key, or unit if not found. |
9090 | | /// @example m.get("name") // "Alice" |
9091 | 1.30k | if (strcmp(method, "get") == 0) { |
9092 | 1.00k | if (arg_count != 1 || args[0].type != VAL_STR) |
9093 | 0 | return eval_err(strdup(".get() expects 1 string argument")); |
9094 | 1.00k | LatValue *found = (LatValue *)lat_map_get(obj.as.map.map, args[0].as.str_val); |
9095 | 1.00k | return eval_ok(found ? value_deep_clone(found) : value_nil()); |
9096 | 1.00k | } |
9097 | | /// @method Map.has(key: String) -> Bool |
9098 | | /// @category Map Methods |
9099 | | /// Check if the map contains the given key. |
9100 | | /// @example m.has("name") // true |
9101 | 302 | if (strcmp(method, "has") == 0) { |
9102 | 112 | if (arg_count != 1 || args[0].type != VAL_STR) |
9103 | 0 | return eval_err(strdup(".has() expects 1 string argument")); |
9104 | 112 | return eval_ok(value_bool(lat_map_contains(obj.as.map.map, args[0].as.str_val))); |
9105 | 112 | } |
9106 | | /// @method Map.keys() -> Array |
9107 | | /// @category Map Methods |
9108 | | /// Return an array of all keys in the map. |
9109 | | /// @example m.keys() // ["name", "age"] |
9110 | 190 | if (strcmp(method, "keys") == 0) { |
9111 | 15 | size_t n = lat_map_len(obj.as.map.map); |
9112 | 15 | LatValue *keys = malloc((n > 0 ? n : 1) * sizeof(LatValue)); |
9113 | 15 | size_t ki = 0; |
9114 | 255 | for (size_t i = 0; i < obj.as.map.map->cap; i++) { |
9115 | 240 | if (obj.as.map.map->entries[i].state == MAP_OCCUPIED) { |
9116 | 25 | keys[ki++] = value_string(obj.as.map.map->entries[i].key); |
9117 | 25 | } |
9118 | 240 | } |
9119 | 15 | LatValue arr = value_array(keys, ki); |
9120 | 15 | free(keys); |
9121 | 15 | return eval_ok(arr); |
9122 | 15 | } |
9123 | | /// @method Map.values() -> Array |
9124 | | /// @category Map Methods |
9125 | | /// Return an array of all values in the map. |
9126 | | /// @example m.values() // ["Alice", 30] |
9127 | 175 | if (strcmp(method, "values") == 0) { |
9128 | 1 | size_t n = lat_map_len(obj.as.map.map); |
9129 | 1 | LatValue *vals = malloc((n > 0 ? n : 1) * sizeof(LatValue)); |
9130 | 1 | size_t vi = 0; |
9131 | 17 | for (size_t i = 0; i < obj.as.map.map->cap; i++) { |
9132 | 16 | if (obj.as.map.map->entries[i].state == MAP_OCCUPIED) { |
9133 | 1 | vals[vi++] = value_deep_clone((LatValue *)obj.as.map.map->entries[i].value); |
9134 | 1 | } |
9135 | 16 | } |
9136 | 1 | LatValue arr = value_array(vals, vi); |
9137 | 1 | free(vals); |
9138 | 1 | return eval_ok(arr); |
9139 | 1 | } |
9140 | | /// @method Map.len() -> Int |
9141 | | /// @category Map Methods |
9142 | | /// Return the number of key-value pairs in the map. |
9143 | | /// @example m.len() // 2 |
9144 | 174 | if (strcmp(method, "len") == 0) { |
9145 | 0 | return eval_ok(value_int((int64_t)lat_map_len(obj.as.map.map))); |
9146 | 0 | } |
9147 | | /// @method Map.entries() -> Array |
9148 | | /// @category Map Methods |
9149 | | /// Return an array of [key, value] pairs. |
9150 | | /// @example m.entries() // [["name", "Alice"], ["age", 30]] |
9151 | 174 | if (strcmp(method, "entries") == 0) { |
9152 | 1 | if (arg_count != 0) return eval_err(strdup(".entries() takes no arguments")); |
9153 | 1 | size_t n = lat_map_len(obj.as.map.map); |
9154 | 1 | LatValue *entries = malloc((n > 0 ? n : 1) * sizeof(LatValue)); |
9155 | 1 | size_t ei = 0; |
9156 | 17 | for (size_t i = 0; i < obj.as.map.map->cap; i++) { |
9157 | 16 | if (obj.as.map.map->entries[i].state == MAP_OCCUPIED) { |
9158 | 1 | LatValue pair_elems[2]; |
9159 | 1 | pair_elems[0] = value_string(obj.as.map.map->entries[i].key); |
9160 | 1 | pair_elems[1] = value_deep_clone((LatValue *)obj.as.map.map->entries[i].value); |
9161 | 1 | entries[ei++] = value_array(pair_elems, 2); |
9162 | 1 | } |
9163 | 16 | } |
9164 | 1 | LatValue arr = value_array(entries, ei); |
9165 | 1 | free(entries); |
9166 | 1 | return eval_ok(arr); |
9167 | 1 | } |
9168 | | /// @method Map.merge(other: Map) -> Unit |
9169 | | /// @category Map Methods |
9170 | | /// Merge another map into this one (mutates in place). |
9171 | | /// @example m.merge(other_map) |
9172 | 173 | if (strcmp(method, "merge") == 0) { |
9173 | 0 | if (arg_count != 1) return eval_err(strdup(".merge() expects exactly 1 argument")); |
9174 | 0 | if (args[0].type != VAL_MAP) return eval_err(strdup(".merge() argument must be a Map")); |
9175 | 0 | LatMap *other = args[0].as.map.map; |
9176 | 0 | for (size_t i = 0; i < other->cap; i++) { |
9177 | 0 | if (other->entries[i].state == MAP_OCCUPIED) { |
9178 | 0 | LatValue cloned = value_deep_clone((LatValue *)other->entries[i].value); |
9179 | 0 | lat_map_set(obj.as.map.map, other->entries[i].key, &cloned); |
9180 | 0 | } |
9181 | 0 | } |
9182 | 0 | return eval_ok(value_unit()); |
9183 | 0 | } |
9184 | | /// @method Map.for_each(fn: Closure) -> Unit |
9185 | | /// @category Map Methods |
9186 | | /// Call fn(key, value) for each entry in the map. |
9187 | | /// @example m.for_each(|k, v| { print(k, v) }) |
9188 | 173 | if (strcmp(method, "for_each") == 0) { |
9189 | 1 | if (arg_count != 1 || args[0].type != VAL_CLOSURE) |
9190 | 0 | return eval_err(strdup(".for_each() expects 1 closure argument")); |
9191 | 17 | for (size_t i = 0; i < obj.as.map.map->cap; i++) { |
9192 | 16 | if (obj.as.map.map->entries[i].state == MAP_OCCUPIED) { |
9193 | 1 | LatValue call_args[2]; |
9194 | 1 | call_args[0] = value_string(obj.as.map.map->entries[i].key); |
9195 | 1 | call_args[1] = value_deep_clone((LatValue *)obj.as.map.map->entries[i].value); |
9196 | 1 | EvalResult r = call_closure(ev, |
9197 | 1 | args[0].as.closure.param_names, |
9198 | 1 | args[0].as.closure.param_count, |
9199 | 1 | args[0].as.closure.body, |
9200 | 1 | args[0].as.closure.captured_env, |
9201 | 1 | call_args, 2, |
9202 | 1 | args[0].as.closure.default_values, args[0].as.closure.has_variadic); |
9203 | 1 | if (!IS_OK(r)) return r; |
9204 | 1 | value_free(&r.value); |
9205 | 1 | } |
9206 | 16 | } |
9207 | 1 | return eval_ok(value_unit()); |
9208 | 1 | } |
9209 | | /// @method Map.filter(fn: Closure) -> Map |
9210 | | /// @category Map Methods |
9211 | | /// Return a new map with only entries where fn(key, value) returns true. |
9212 | | /// @example m.filter(|k, v| { v > 0 }) |
9213 | 172 | if (strcmp(method, "filter") == 0) { |
9214 | 2 | if (arg_count != 1 || args[0].type != VAL_CLOSURE) |
9215 | 0 | return eval_err(strdup(".filter() expects 1 closure argument")); |
9216 | 2 | LatValue result = value_map_new(); |
9217 | 34 | for (size_t i = 0; i < obj.as.map.map->cap; i++) { |
9218 | 32 | if (obj.as.map.map->entries[i].state == MAP_OCCUPIED) { |
9219 | 4 | LatValue call_args[2]; |
9220 | 4 | call_args[0] = value_string(obj.as.map.map->entries[i].key); |
9221 | 4 | call_args[1] = value_deep_clone((LatValue *)obj.as.map.map->entries[i].value); |
9222 | 4 | EvalResult r = call_closure(ev, |
9223 | 4 | args[0].as.closure.param_names, |
9224 | 4 | args[0].as.closure.param_count, |
9225 | 4 | args[0].as.closure.body, |
9226 | 4 | args[0].as.closure.captured_env, |
9227 | 4 | call_args, 2, |
9228 | 4 | args[0].as.closure.default_values, args[0].as.closure.has_variadic); |
9229 | 4 | if (!IS_OK(r)) { value_free(&result); return r; } |
9230 | 4 | if (value_is_truthy(&r.value)) { |
9231 | 2 | LatValue cloned = value_deep_clone((LatValue *)obj.as.map.map->entries[i].value); |
9232 | 2 | lat_map_set(result.as.map.map, obj.as.map.map->entries[i].key, &cloned); |
9233 | 2 | } |
9234 | 4 | value_free(&r.value); |
9235 | 4 | } |
9236 | 32 | } |
9237 | 2 | return eval_ok(result); |
9238 | 2 | } |
9239 | | /// @method Map.map(fn: Closure) -> Map |
9240 | | /// @category Map Methods |
9241 | | /// Return a new map with values transformed by fn(key, value). |
9242 | | /// @example m.map(|k, v| { v * 2 }) |
9243 | 170 | if (strcmp(method, "map") == 0) { |
9244 | 2 | if (arg_count != 1 || args[0].type != VAL_CLOSURE) |
9245 | 0 | return eval_err(strdup(".map() expects 1 closure argument")); |
9246 | 2 | LatValue result = value_map_new(); |
9247 | 34 | for (size_t i = 0; i < obj.as.map.map->cap; i++) { |
9248 | 32 | if (obj.as.map.map->entries[i].state == MAP_OCCUPIED) { |
9249 | 4 | LatValue call_args[2]; |
9250 | 4 | call_args[0] = value_string(obj.as.map.map->entries[i].key); |
9251 | 4 | call_args[1] = value_deep_clone((LatValue *)obj.as.map.map->entries[i].value); |
9252 | 4 | EvalResult r = call_closure(ev, |
9253 | 4 | args[0].as.closure.param_names, |
9254 | 4 | args[0].as.closure.param_count, |
9255 | 4 | args[0].as.closure.body, |
9256 | 4 | args[0].as.closure.captured_env, |
9257 | 4 | call_args, 2, |
9258 | 4 | args[0].as.closure.default_values, args[0].as.closure.has_variadic); |
9259 | 4 | if (!IS_OK(r)) { value_free(&result); return r; } |
9260 | 4 | lat_map_set(result.as.map.map, obj.as.map.map->entries[i].key, &r.value); |
9261 | 4 | } |
9262 | 32 | } |
9263 | 2 | return eval_ok(result); |
9264 | 2 | } |
9265 | 170 | } |
9266 | | /* ── String methods ── */ |
9267 | 401 | if (obj.type == VAL_STR) { |
9268 | | /// @method String.contains(substr: String) -> Bool |
9269 | | /// @category String Methods |
9270 | | /// Check if the string contains a substring. |
9271 | | /// @example "hello world".contains("world") // true |
9272 | 192 | if (strcmp(method, "contains") == 0) { |
9273 | 5 | if (arg_count != 1 || args[0].type != VAL_STR) |
9274 | 0 | return eval_err(strdup(".contains() expects 1 string argument")); |
9275 | 5 | return eval_ok(value_bool(lat_str_contains(obj.as.str_val, args[0].as.str_val))); |
9276 | 5 | } |
9277 | | /// @method String.starts_with(prefix: String) -> Bool |
9278 | | /// @category String Methods |
9279 | | /// Check if the string starts with the given prefix. |
9280 | | /// @example "hello".starts_with("he") // true |
9281 | 187 | if (strcmp(method, "starts_with") == 0) { |
9282 | 45 | if (arg_count != 1 || args[0].type != VAL_STR) |
9283 | 0 | return eval_err(strdup(".starts_with() expects 1 string argument")); |
9284 | 45 | return eval_ok(value_bool(lat_str_starts_with(obj.as.str_val, args[0].as.str_val))); |
9285 | 45 | } |
9286 | | /// @method String.ends_with(suffix: String) -> Bool |
9287 | | /// @category String Methods |
9288 | | /// Check if the string ends with the given suffix. |
9289 | | /// @example "hello".ends_with("lo") // true |
9290 | 142 | if (strcmp(method, "ends_with") == 0) { |
9291 | 2 | if (arg_count != 1 || args[0].type != VAL_STR) |
9292 | 0 | return eval_err(strdup(".ends_with() expects 1 string argument")); |
9293 | 2 | return eval_ok(value_bool(lat_str_ends_with(obj.as.str_val, args[0].as.str_val))); |
9294 | 2 | } |
9295 | | /// @method String.trim() -> String |
9296 | | /// @category String Methods |
9297 | | /// Remove leading and trailing whitespace. |
9298 | | /// @example " hello ".trim() // "hello" |
9299 | 140 | if (strcmp(method, "trim") == 0) { |
9300 | 43 | return eval_ok(value_string_owned(lat_str_trim(obj.as.str_val))); |
9301 | 43 | } |
9302 | | /// @method String.to_upper() -> String |
9303 | | /// @category String Methods |
9304 | | /// Convert the string to uppercase. |
9305 | | /// @example "hello".to_upper() // "HELLO" |
9306 | 97 | if (strcmp(method, "to_upper") == 0) { |
9307 | 3 | return eval_ok(value_string_owned(lat_str_to_upper(obj.as.str_val))); |
9308 | 3 | } |
9309 | | /// @method String.to_lower() -> String |
9310 | | /// @category String Methods |
9311 | | /// Convert the string to lowercase. |
9312 | | /// @example "HELLO".to_lower() // "hello" |
9313 | 94 | if (strcmp(method, "to_lower") == 0) { |
9314 | 2 | return eval_ok(value_string_owned(lat_str_to_lower(obj.as.str_val))); |
9315 | 2 | } |
9316 | | /// @method String.replace(old: String, new: String) -> String |
9317 | | /// @category String Methods |
9318 | | /// Replace all occurrences of a substring. |
9319 | | /// @example "hello world".replace("world", "there") // "hello there" |
9320 | 92 | if (strcmp(method, "replace") == 0) { |
9321 | 2 | if (arg_count != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) |
9322 | 0 | return eval_err(strdup(".replace() expects 2 string arguments")); |
9323 | 2 | return eval_ok(value_string_owned(lat_str_replace(obj.as.str_val, args[0].as.str_val, args[1].as.str_val))); |
9324 | 2 | } |
9325 | | /// @method String.split(sep: String) -> Array |
9326 | | /// @category String Methods |
9327 | | /// Split the string by a separator, returning an array of parts. |
9328 | | /// @example "a,b,c".split(",") // ["a", "b", "c"] |
9329 | 90 | if (strcmp(method, "split") == 0) { |
9330 | 11 | if (arg_count != 1 || args[0].type != VAL_STR) |
9331 | 0 | return eval_err(strdup(".split() expects 1 string argument")); |
9332 | 11 | size_t count; |
9333 | 11 | char **parts = lat_str_split(obj.as.str_val, args[0].as.str_val, &count); |
9334 | 11 | LatValue *elems = malloc(count * sizeof(LatValue)); |
9335 | 27 | for (size_t i = 0; i < count; i++) { |
9336 | 16 | elems[i] = value_string_owned(parts[i]); |
9337 | 16 | } |
9338 | 11 | free(parts); |
9339 | 11 | LatValue arr = value_array(elems, count); |
9340 | 11 | free(elems); |
9341 | 11 | return eval_ok(arr); |
9342 | 11 | } |
9343 | | /// @method String.index_of(substr: String) -> Int |
9344 | | /// @category String Methods |
9345 | | /// Return the index of the first occurrence of substr, or -1 if not found. |
9346 | | /// @example "hello".index_of("ll") // 2 |
9347 | 79 | if (strcmp(method, "index_of") == 0) { |
9348 | 27 | if (arg_count != 1 || args[0].type != VAL_STR) |
9349 | 0 | return eval_err(strdup(".index_of() expects 1 string argument")); |
9350 | 27 | return eval_ok(value_int(lat_str_index_of(obj.as.str_val, args[0].as.str_val))); |
9351 | 27 | } |
9352 | | /// @method String.substring(start: Int, end: Int) -> String |
9353 | | /// @category String Methods |
9354 | | /// Extract a substring from start (inclusive) to end (exclusive). |
9355 | | /// @example "hello".substring(1, 4) // "ell" |
9356 | 52 | if (strcmp(method, "substring") == 0) { |
9357 | 31 | if (arg_count != 2 || args[0].type != VAL_INT || args[1].type != VAL_INT) |
9358 | 0 | return eval_err(strdup(".substring() expects 2 integer arguments")); |
9359 | 31 | return eval_ok(value_string_owned(lat_str_substring(obj.as.str_val, args[0].as.int_val, args[1].as.int_val))); |
9360 | 31 | } |
9361 | | /// @method String.chars() -> Array |
9362 | | /// @category String Methods |
9363 | | /// Split the string into an array of single-character strings. |
9364 | | /// @example "abc".chars() // ["a", "b", "c"] |
9365 | 21 | if (strcmp(method, "chars") == 0) { |
9366 | 7 | size_t slen = strlen(obj.as.str_val); |
9367 | 7 | LatValue *elems = malloc((slen > 0 ? slen : 1) * sizeof(LatValue)); |
9368 | 94 | for (size_t i = 0; i < slen; i++) { |
9369 | 87 | char buf[2] = { obj.as.str_val[i], '\0' }; |
9370 | 87 | elems[i] = value_string(buf); |
9371 | 87 | } |
9372 | 7 | LatValue arr = value_array(elems, slen); |
9373 | 7 | free(elems); |
9374 | 7 | return eval_ok(arr); |
9375 | 7 | } |
9376 | | /// @method String.bytes() -> Array |
9377 | | /// @category String Methods |
9378 | | /// Return an array of byte values (integers) for the string. |
9379 | | /// @example "AB".bytes() // [65, 66] |
9380 | 14 | if (strcmp(method, "bytes") == 0) { |
9381 | 1 | size_t slen = strlen(obj.as.str_val); |
9382 | 1 | LatValue *elems = malloc((slen > 0 ? slen : 1) * sizeof(LatValue)); |
9383 | 4 | for (size_t i = 0; i < slen; i++) { |
9384 | 3 | elems[i] = value_int((int64_t)(unsigned char)obj.as.str_val[i]); |
9385 | 3 | } |
9386 | 1 | LatValue arr = value_array(elems, slen); |
9387 | 1 | free(elems); |
9388 | 1 | return eval_ok(arr); |
9389 | 1 | } |
9390 | | /// @method String.reverse() -> String |
9391 | | /// @category String Methods |
9392 | | /// Return the string with characters in reverse order. |
9393 | | /// @example "hello".reverse() // "olleh" |
9394 | 13 | if (strcmp(method, "reverse") == 0) { |
9395 | 2 | return eval_ok(value_string_owned(lat_str_reverse(obj.as.str_val))); |
9396 | 2 | } |
9397 | | /// @method String.repeat(n: Int) -> String |
9398 | | /// @category String Methods |
9399 | | /// Repeat the string n times. |
9400 | | /// @example "ab".repeat(3) // "ababab" |
9401 | 11 | if (strcmp(method, "repeat") == 0) { |
9402 | 2 | if (arg_count != 1 || args[0].type != VAL_INT) |
9403 | 0 | return eval_err(strdup(".repeat() expects 1 integer argument")); |
9404 | 2 | return eval_ok(value_string_owned(lat_str_repeat(obj.as.str_val, (size_t)args[0].as.int_val))); |
9405 | 2 | } |
9406 | | /// @method String.trim_start() -> String |
9407 | | /// @category String Methods |
9408 | | /// Remove leading whitespace. |
9409 | | /// @example " hello".trim_start() // "hello" |
9410 | 9 | if (strcmp(method, "trim_start") == 0) { |
9411 | 1 | if (arg_count != 0) return eval_err(strdup(".trim_start() takes no arguments")); |
9412 | 1 | const char *s = obj.as.str_val; |
9413 | 1 | size_t len = strlen(s); |
9414 | 1 | size_t start = 0; |
9415 | 3 | while (start < len && isspace((unsigned char)s[start])) start++; |
9416 | 1 | char *result = malloc(len - start + 1); |
9417 | 1 | memcpy(result, s + start, len - start); |
9418 | 1 | result[len - start] = '\0'; |
9419 | 1 | return eval_ok(value_string_owned(result)); |
9420 | 1 | } |
9421 | | /// @method String.trim_end() -> String |
9422 | | /// @category String Methods |
9423 | | /// Remove trailing whitespace. |
9424 | | /// @example "hello ".trim_end() // "hello" |
9425 | 8 | if (strcmp(method, "trim_end") == 0) { |
9426 | 1 | if (arg_count != 0) return eval_err(strdup(".trim_end() takes no arguments")); |
9427 | 1 | const char *s = obj.as.str_val; |
9428 | 1 | size_t len = strlen(s); |
9429 | 1 | size_t end = len; |
9430 | 3 | while (end > 0 && isspace((unsigned char)s[end - 1])) end--; |
9431 | 1 | char *result = malloc(end + 1); |
9432 | 1 | memcpy(result, s, end); |
9433 | 1 | result[end] = '\0'; |
9434 | 1 | return eval_ok(value_string_owned(result)); |
9435 | 1 | } |
9436 | | /// @method String.pad_left(n: Int, ch: String) -> String |
9437 | | /// @category String Methods |
9438 | | /// Pad the string on the left to reach length n using character ch. |
9439 | | /// @example "42".pad_left(5, "0") // "00042" |
9440 | 7 | if (strcmp(method, "pad_left") == 0) { |
9441 | 1 | if (arg_count != 2) return eval_err(strdup(".pad_left() expects 2 arguments (n, ch)")); |
9442 | 1 | if (args[0].type != VAL_INT) return eval_err(strdup(".pad_left() first argument must be an integer")); |
9443 | 1 | if (args[1].type != VAL_STR) return eval_err(strdup(".pad_left() second argument must be a string")); |
9444 | 1 | if (strlen(args[1].as.str_val) != 1) return eval_err(strdup(".pad_left() padding must be a single character")); |
9445 | 1 | const char *s = obj.as.str_val; |
9446 | 1 | size_t slen = strlen(s); |
9447 | 1 | size_t target = (size_t)args[0].as.int_val; |
9448 | 1 | if (slen >= target) return eval_ok(value_string(s)); |
9449 | 1 | size_t pad_count = target - slen; |
9450 | 1 | char *result = malloc(target + 1); |
9451 | 1 | char ch = args[1].as.str_val[0]; |
9452 | 1 | memset(result, ch, pad_count); |
9453 | 1 | memcpy(result + pad_count, s, slen); |
9454 | 1 | result[target] = '\0'; |
9455 | 1 | return eval_ok(value_string_owned(result)); |
9456 | 1 | } |
9457 | | /// @method String.pad_right(n: Int, ch: String) -> String |
9458 | | /// @category String Methods |
9459 | | /// Pad the string on the right to reach length n using character ch. |
9460 | | /// @example "42".pad_right(5, "0") // "42000" |
9461 | 6 | if (strcmp(method, "pad_right") == 0) { |
9462 | 1 | if (arg_count != 2) return eval_err(strdup(".pad_right() expects 2 arguments (n, ch)")); |
9463 | 1 | if (args[0].type != VAL_INT) return eval_err(strdup(".pad_right() first argument must be an integer")); |
9464 | 1 | if (args[1].type != VAL_STR) return eval_err(strdup(".pad_right() second argument must be a string")); |
9465 | 1 | if (strlen(args[1].as.str_val) != 1) return eval_err(strdup(".pad_right() padding must be a single character")); |
9466 | 1 | const char *s = obj.as.str_val; |
9467 | 1 | size_t slen = strlen(s); |
9468 | 1 | size_t target = (size_t)args[0].as.int_val; |
9469 | 1 | if (slen >= target) return eval_ok(value_string(s)); |
9470 | 1 | size_t pad_count = target - slen; |
9471 | 1 | char *result = malloc(target + 1); |
9472 | 1 | memcpy(result, s, slen); |
9473 | 1 | char ch = args[1].as.str_val[0]; |
9474 | 1 | memset(result + slen, ch, pad_count); |
9475 | 1 | result[target] = '\0'; |
9476 | 1 | return eval_ok(value_string_owned(result)); |
9477 | 1 | } |
9478 | | /// @method String.count(substr: String) -> Int |
9479 | | /// @category String Methods |
9480 | | /// Count non-overlapping occurrences of a substring. |
9481 | | /// @example "ababa".count("ab") // 2 |
9482 | 5 | if (strcmp(method, "count") == 0) { |
9483 | 3 | if (arg_count != 1 || args[0].type != VAL_STR) |
9484 | 0 | return eval_err(strdup(".count() expects 1 string argument")); |
9485 | 3 | const char *haystack = obj.as.str_val; |
9486 | 3 | const char *needle = args[0].as.str_val; |
9487 | 3 | size_t needle_len = strlen(needle); |
9488 | 3 | int64_t count = 0; |
9489 | 3 | if (needle_len == 0) { |
9490 | 0 | return eval_err(strdup(".count() substring must not be empty")); |
9491 | 0 | } |
9492 | 3 | const char *p = haystack; |
9493 | 6 | while ((p = strstr(p, needle)) != NULL) { |
9494 | 3 | count++; |
9495 | 3 | p += needle_len; |
9496 | 3 | } |
9497 | 3 | return eval_ok(value_int(count)); |
9498 | 3 | } |
9499 | | /// @method String.is_empty() -> Bool |
9500 | | /// @category String Methods |
9501 | | /// Check if the string is empty. |
9502 | | /// @example "".is_empty() // true |
9503 | 2 | if (strcmp(method, "is_empty") == 0) { |
9504 | 2 | if (arg_count != 0) |
9505 | 0 | return eval_err(strdup(".is_empty() takes no arguments")); |
9506 | 2 | return eval_ok(value_bool(obj.as.str_val[0] == '\0')); |
9507 | 2 | } |
9508 | 2 | } |
9509 | | |
9510 | 209 | if (strcmp(method, "get") == 0 && obj.type != VAL_REF) { |
9511 | 0 | if (obj.type != VAL_STRUCT) return eval_err(strdup(".get() is not defined on non-struct")); |
9512 | 0 | if (arg_count != 1) return eval_err(strdup(".get() expects exactly 1 argument")); |
9513 | 0 | if (args[0].type != VAL_STR) return eval_err(strdup(".get() key must be a string")); |
9514 | 0 | for (size_t i = 0; i < obj.as.strct.field_count; i++) { |
9515 | 0 | if (strcmp(obj.as.strct.field_names[i], args[0].as.str_val) == 0) { |
9516 | 0 | return eval_ok(value_deep_clone(&obj.as.strct.field_values[i])); |
9517 | 0 | } |
9518 | 0 | } |
9519 | 0 | char *err = NULL; |
9520 | 0 | (void)asprintf(&err, "struct has no field '%s'", args[0].as.str_val); |
9521 | 0 | return eval_err(err); |
9522 | 0 | } |
9523 | | |
9524 | | /* ── Callable struct fields: obj.method(args) where field is a closure ── */ |
9525 | 209 | if (obj.type == VAL_STRUCT) { |
9526 | 20 | for (size_t i = 0; i < obj.as.strct.field_count; i++) { |
9527 | 14 | if (strcmp(obj.as.strct.field_names[i], method) == 0 && |
9528 | 14 | obj.as.strct.field_values[i].type == VAL_CLOSURE) { |
9529 | 4 | LatValue *cl = &obj.as.strct.field_values[i]; |
9530 | | /* Prepend self (the struct) as the first argument */ |
9531 | 4 | size_t total = 1 + arg_count; |
9532 | 4 | LatValue *full_args = malloc(total * sizeof(LatValue)); |
9533 | 4 | full_args[0] = value_deep_clone(&obj); |
9534 | 6 | for (size_t j = 0; j < arg_count; j++) { |
9535 | 2 | full_args[j + 1] = value_deep_clone(&args[j]); |
9536 | 2 | } |
9537 | 4 | EvalResult r = call_closure(ev, |
9538 | 4 | cl->as.closure.param_names, cl->as.closure.param_count, |
9539 | 4 | cl->as.closure.body, cl->as.closure.captured_env, |
9540 | 4 | full_args, total, |
9541 | 4 | cl->as.closure.default_values, cl->as.closure.has_variadic); |
9542 | | /* call_closure takes ownership of args via env_define; |
9543 | | env_pop_scope frees them — do NOT value_free here */ |
9544 | 4 | free(full_args); |
9545 | 4 | return r; |
9546 | 4 | } |
9547 | 14 | } |
9548 | 10 | } |
9549 | | |
9550 | | /* ── Channel methods ── */ |
9551 | 205 | if (obj.type == VAL_CHANNEL) { |
9552 | 21 | LatChannel *ch = obj.as.channel.ch; |
9553 | | /// @method Channel.send(val: Any) -> Unit |
9554 | | /// @category Channel Methods |
9555 | | /// Send a crystal (frozen) value on the channel. |
9556 | | /// @example ch.send(freeze(42)) |
9557 | 21 | if (strcmp(method, "send") == 0) { |
9558 | 10 | if (arg_count != 1) |
9559 | 0 | return eval_err(strdup(".send() expects exactly 1 argument")); |
9560 | 10 | if (!value_is_crystal(&args[0]) && args[0].type != VAL_INT && |
9561 | 10 | args[0].type != VAL_FLOAT && args[0].type != VAL_BOOL && |
9562 | 10 | args[0].type != VAL_UNIT) { |
9563 | 1 | return eval_err(strdup("can only send crystal (frozen) values on a channel")); |
9564 | 1 | } |
9565 | | /* Deep-clone into malloc-backed memory (no heap/arena pointers) */ |
9566 | 9 | DualHeap *saved_heap = ev->heap; |
9567 | 9 | value_set_heap(NULL); |
9568 | 9 | value_set_arena(NULL); |
9569 | 9 | LatValue detached = value_deep_clone(&args[0]); |
9570 | 9 | value_set_heap(saved_heap); |
9571 | 9 | bool ok = channel_send(ch, detached); |
9572 | 9 | if (!ok) return eval_err(strdup("cannot send on a closed channel")); |
9573 | 9 | return eval_ok(value_unit()); |
9574 | 9 | } |
9575 | | /// @method Channel.recv() -> Any|Unit |
9576 | | /// @category Channel Methods |
9577 | | /// Receive a value from the channel, blocking until available. Returns unit if closed. |
9578 | | /// @example ch.recv() // 42 |
9579 | 11 | if (strcmp(method, "recv") == 0) { |
9580 | 8 | if (arg_count != 0) |
9581 | 0 | return eval_err(strdup(".recv() takes no arguments")); |
9582 | 8 | bool ok; |
9583 | 8 | LatValue val = channel_recv(ch, &ok); |
9584 | 8 | if (!ok) return eval_ok(value_unit()); |
9585 | 7 | return eval_ok(val); |
9586 | 8 | } |
9587 | | /// @method Channel.close() -> Unit |
9588 | | /// @category Channel Methods |
9589 | | /// Close the channel, preventing further sends. |
9590 | | /// @example ch.close() |
9591 | 3 | if (strcmp(method, "close") == 0) { |
9592 | 3 | if (arg_count != 0) |
9593 | 0 | return eval_err(strdup(".close() takes no arguments")); |
9594 | 3 | channel_close(ch); |
9595 | 3 | return eval_ok(value_unit()); |
9596 | 3 | } |
9597 | 3 | } |
9598 | | |
9599 | | /* Fallback: check for callable field (e.g. module map with closure values). |
9600 | | * call_closure consumes args, but the caller also frees them, so we |
9601 | | * deep-clone args before passing. */ |
9602 | 184 | if (obj.type == VAL_MAP) { |
9603 | 168 | LatValue *field = (LatValue *)lat_map_get(obj.as.map.map, method); |
9604 | 168 | if (field && field->type == VAL_CLOSURE) { |
9605 | 168 | if (field->as.closure.native_fn && !field->as.closure.body |
9606 | 168 | && field->as.closure.default_values == (struct Expr **)(uintptr_t)0x1) { |
9607 | | /* VM-style native from builtin module */ |
9608 | 2 | typedef LatValue (*VMNativeFn)(LatValue *, int); |
9609 | 2 | VMNativeFn fn = (VMNativeFn)field->as.closure.native_fn; |
9610 | 2 | LatRuntime *prev_rt = lat_runtime_current(); |
9611 | 2 | LatRuntime tmp_rt; |
9612 | 2 | if (!prev_rt) { |
9613 | 2 | memset(&tmp_rt, 0, sizeof(tmp_rt)); |
9614 | 2 | lat_runtime_set_current(&tmp_rt); |
9615 | 2 | } |
9616 | 2 | LatRuntime *rt = lat_runtime_current(); |
9617 | 2 | LatValue result = fn(args, (int)arg_count); |
9618 | 2 | EvalResult res; |
9619 | 2 | if (rt->error) { |
9620 | 0 | char *msg = rt->error; |
9621 | 0 | rt->error = NULL; |
9622 | 0 | value_free(&result); |
9623 | 0 | res = eval_err(msg); |
9624 | 2 | } else { |
9625 | 2 | res = eval_ok(result); |
9626 | 2 | } |
9627 | 2 | if (!prev_rt) lat_runtime_set_current(NULL); |
9628 | 2 | return res; |
9629 | 2 | } |
9630 | 166 | LatValue *cloned_args = malloc(arg_count * sizeof(LatValue)); |
9631 | 410 | for (size_t i = 0; i < arg_count; i++) |
9632 | 244 | cloned_args[i] = value_deep_clone(&args[i]); |
9633 | 166 | EvalResult res = call_closure(ev, |
9634 | 166 | field->as.closure.param_names, |
9635 | 166 | field->as.closure.param_count, |
9636 | 166 | field->as.closure.body, |
9637 | 166 | field->as.closure.captured_env, |
9638 | 166 | cloned_args, arg_count, |
9639 | 166 | field->as.closure.default_values, field->as.closure.has_variadic); |
9640 | 166 | free(cloned_args); |
9641 | 166 | return res; |
9642 | 168 | } |
9643 | 168 | } else if (obj.type == VAL_STRUCT) { |
9644 | 12 | for (size_t i = 0; i < obj.as.strct.field_count; i++) { |
9645 | 6 | if (strcmp(obj.as.strct.field_names[i], method) == 0 && |
9646 | 6 | obj.as.strct.field_values[i].type == VAL_CLOSURE) { |
9647 | 0 | LatValue *field = &obj.as.strct.field_values[i]; |
9648 | 0 | LatValue *cloned_args = malloc(arg_count * sizeof(LatValue)); |
9649 | 0 | for (size_t j = 0; j < arg_count; j++) |
9650 | 0 | cloned_args[j] = value_deep_clone(&args[j]); |
9651 | 0 | EvalResult res = call_closure(ev, |
9652 | 0 | field->as.closure.param_names, |
9653 | 0 | field->as.closure.param_count, |
9654 | 0 | field->as.closure.body, |
9655 | 0 | field->as.closure.captured_env, |
9656 | 0 | cloned_args, arg_count, |
9657 | 0 | field->as.closure.default_values, field->as.closure.has_variadic); |
9658 | 0 | free(cloned_args); |
9659 | 0 | return res; |
9660 | 0 | } |
9661 | 6 | } |
9662 | 6 | } |
9663 | | |
9664 | | /* ── Trait impl method dispatch ── */ |
9665 | 16 | { |
9666 | 16 | const char *type_name = NULL; |
9667 | 16 | if (obj.type == VAL_STRUCT) type_name = obj.as.strct.name; |
9668 | 10 | else if (obj.type == VAL_INT) type_name = "Int"; |
9669 | 10 | else if (obj.type == VAL_FLOAT) type_name = "Float"; |
9670 | 10 | else if (obj.type == VAL_STR) type_name = "String"; |
9671 | 10 | else if (obj.type == VAL_BOOL) type_name = "Bool"; |
9672 | 10 | else if (obj.type == VAL_ARRAY) type_name = "Array"; |
9673 | 10 | else if (obj.type == VAL_MAP) type_name = "Map"; |
9674 | | |
9675 | 16 | if (type_name) { |
9676 | | /* Search all impl blocks for this type */ |
9677 | 51 | for (size_t i = 0; i < ev->impl_registry.cap; i++) { |
9678 | 51 | if (ev->impl_registry.entries[i].state != MAP_OCCUPIED) continue; |
9679 | 7 | ImplBlock **ib_ptr = (ImplBlock **)ev->impl_registry.entries[i].value; |
9680 | 7 | ImplBlock *ib = *ib_ptr; |
9681 | 7 | if (strcmp(ib->type_name, type_name) != 0) continue; |
9682 | 7 | for (size_t m = 0; m < ib->method_count; m++) { |
9683 | 7 | if (strcmp(ib->methods[m].name, method) != 0) continue; |
9684 | | /* Found a matching impl method — call it with self as first arg. |
9685 | | * call_fn takes ownership of args via env_define, so do NOT free them. */ |
9686 | 6 | FnDecl *fn = &ib->methods[m]; |
9687 | 6 | size_t total = 1 + arg_count; |
9688 | 6 | LatValue *full_args = malloc(total * sizeof(LatValue)); |
9689 | 6 | full_args[0] = value_deep_clone(&obj); |
9690 | 7 | for (size_t j = 0; j < arg_count; j++) |
9691 | 1 | full_args[j + 1] = value_deep_clone(&args[j]); |
9692 | 6 | EvalResult r = call_fn(ev, fn, full_args, total, NULL); |
9693 | 6 | free(full_args); |
9694 | 6 | return r; |
9695 | 7 | } |
9696 | 6 | } |
9697 | 6 | } |
9698 | 16 | } |
9699 | | |
9700 | | /* ── Ref methods ── */ |
9701 | 10 | if (obj.type == VAL_REF) { |
9702 | 10 | LatRef *ref = obj.as.ref.ref; |
9703 | 10 | LatValue *inner = &ref->value; |
9704 | | |
9705 | | /* Ref-specific methods */ |
9706 | | /// @method Ref.get() -> Any |
9707 | | /// @category Ref Methods |
9708 | | /// Return a deep clone of the wrapped value. |
9709 | | /// @example r.get() |
9710 | 10 | if (strcmp(method, "get") == 0 && arg_count == 0) { |
9711 | 3 | return eval_ok(value_deep_clone(inner)); |
9712 | 3 | } |
9713 | | /// @method Ref.deref() -> Any |
9714 | | /// @category Ref Methods |
9715 | | /// Alias for get(). Return a deep clone of the wrapped value. |
9716 | | /// @example r.deref() |
9717 | 7 | if (strcmp(method, "deref") == 0 && arg_count == 0) { |
9718 | 1 | return eval_ok(value_deep_clone(inner)); |
9719 | 1 | } |
9720 | | /// @method Ref.set(value: Any) -> Unit |
9721 | | /// @category Ref Methods |
9722 | | /// Replace the inner value (all holders see the change). |
9723 | | /// @example r.set(42) |
9724 | 6 | if (strcmp(method, "set") == 0 && arg_count == 1) { |
9725 | 2 | if (obj.phase == VTAG_CRYSTAL) |
9726 | 0 | return eval_err(strdup("cannot set on a frozen Ref")); |
9727 | 2 | value_free(inner); |
9728 | 2 | *inner = value_deep_clone(&args[0]); |
9729 | 2 | return eval_ok(value_unit()); |
9730 | 2 | } |
9731 | | /// @method Ref.inner_type() -> String |
9732 | | /// @category Ref Methods |
9733 | | /// Return the type name of the wrapped value. |
9734 | | /// @example r.inner_type() |
9735 | 4 | if (strcmp(method, "inner_type") == 0 && arg_count == 0) { |
9736 | 2 | return eval_ok(value_string(value_type_name(inner))); |
9737 | 2 | } |
9738 | | |
9739 | | /* Map proxy (when inner is VAL_MAP) */ |
9740 | 2 | if (inner->type == VAL_MAP) { |
9741 | 2 | if (strcmp(method, "get") == 0 && arg_count == 1) { |
9742 | 0 | if (args[0].type != VAL_STR) return eval_ok(value_nil()); |
9743 | 0 | LatValue *found = lat_map_get(inner->as.map.map, args[0].as.str_val); |
9744 | 0 | return eval_ok(found ? value_deep_clone(found) : value_nil()); |
9745 | 0 | } |
9746 | 2 | if (strcmp(method, "set") == 0 && arg_count == 2) { |
9747 | 0 | if (obj.phase == VTAG_CRYSTAL) |
9748 | 0 | return eval_err(strdup("cannot set on a frozen Ref")); |
9749 | 0 | if (args[0].type != VAL_STR) |
9750 | 0 | return eval_err(strdup(".set() key must be a string")); |
9751 | 0 | LatValue *old = (LatValue *)lat_map_get(inner->as.map.map, args[0].as.str_val); |
9752 | 0 | if (old) value_free(old); |
9753 | 0 | LatValue cloned = value_deep_clone(&args[1]); |
9754 | 0 | lat_map_set(inner->as.map.map, args[0].as.str_val, &cloned); |
9755 | 0 | return eval_ok(value_unit()); |
9756 | 0 | } |
9757 | 2 | if (strcmp(method, "has") == 0 && arg_count == 1) { |
9758 | 2 | bool found = args[0].type == VAL_STR && lat_map_contains(inner->as.map.map, args[0].as.str_val); |
9759 | 2 | return eval_ok(value_bool(found)); |
9760 | 2 | } |
9761 | 0 | if (strcmp(method, "contains") == 0 && arg_count == 1) { |
9762 | 0 | bool found = false; |
9763 | 0 | for (size_t i = 0; i < inner->as.map.map->cap; i++) { |
9764 | 0 | if (inner->as.map.map->entries[i].state != MAP_OCCUPIED) continue; |
9765 | 0 | LatValue *mv = (LatValue *)inner->as.map.map->entries[i].value; |
9766 | 0 | if (value_eq(mv, &args[0])) { found = true; break; } |
9767 | 0 | } |
9768 | 0 | return eval_ok(value_bool(found)); |
9769 | 0 | } |
9770 | 0 | if (strcmp(method, "keys") == 0 && arg_count == 0) { |
9771 | 0 | size_t n = lat_map_len(inner->as.map.map); |
9772 | 0 | LatValue *elems = malloc((n > 0 ? n : 1) * sizeof(LatValue)); |
9773 | 0 | size_t ei = 0; |
9774 | 0 | for (size_t i = 0; i < inner->as.map.map->cap; i++) { |
9775 | 0 | if (inner->as.map.map->entries[i].state != MAP_OCCUPIED) continue; |
9776 | 0 | elems[ei++] = value_string(inner->as.map.map->entries[i].key); |
9777 | 0 | } |
9778 | 0 | LatValue arr = value_array(elems, ei); free(elems); |
9779 | 0 | return eval_ok(arr); |
9780 | 0 | } |
9781 | 0 | if (strcmp(method, "values") == 0 && arg_count == 0) { |
9782 | 0 | size_t n = lat_map_len(inner->as.map.map); |
9783 | 0 | LatValue *elems = malloc((n > 0 ? n : 1) * sizeof(LatValue)); |
9784 | 0 | size_t ei = 0; |
9785 | 0 | for (size_t i = 0; i < inner->as.map.map->cap; i++) { |
9786 | 0 | if (inner->as.map.map->entries[i].state != MAP_OCCUPIED) continue; |
9787 | 0 | LatValue *mv = (LatValue *)inner->as.map.map->entries[i].value; |
9788 | 0 | elems[ei++] = value_deep_clone(mv); |
9789 | 0 | } |
9790 | 0 | LatValue arr = value_array(elems, ei); free(elems); |
9791 | 0 | return eval_ok(arr); |
9792 | 0 | } |
9793 | 0 | if (strcmp(method, "entries") == 0 && arg_count == 0) { |
9794 | 0 | size_t n = lat_map_len(inner->as.map.map); |
9795 | 0 | LatValue *elems = malloc((n > 0 ? n : 1) * sizeof(LatValue)); |
9796 | 0 | size_t ei = 0; |
9797 | 0 | for (size_t i = 0; i < inner->as.map.map->cap; i++) { |
9798 | 0 | if (inner->as.map.map->entries[i].state != MAP_OCCUPIED) continue; |
9799 | 0 | LatValue pair[2]; |
9800 | 0 | pair[0] = value_string(inner->as.map.map->entries[i].key); |
9801 | 0 | pair[1] = value_deep_clone((LatValue *)inner->as.map.map->entries[i].value); |
9802 | 0 | elems[ei++] = value_array(pair, 2); |
9803 | 0 | } |
9804 | 0 | LatValue arr = value_array(elems, ei); free(elems); |
9805 | 0 | return eval_ok(arr); |
9806 | 0 | } |
9807 | 0 | if (strcmp(method, "len") == 0 && arg_count == 0) { |
9808 | 0 | return eval_ok(value_int((int64_t)lat_map_len(inner->as.map.map))); |
9809 | 0 | } |
9810 | 0 | } |
9811 | | |
9812 | | /* Array proxy */ |
9813 | 0 | if (inner->type == VAL_ARRAY) { |
9814 | 0 | if (strcmp(method, "len") == 0 && arg_count == 0) |
9815 | 0 | return eval_ok(value_int((int64_t)inner->as.array.len)); |
9816 | 0 | if (strcmp(method, "contains") == 0 && arg_count == 1) { |
9817 | 0 | for (size_t i = 0; i < inner->as.array.len; i++) { |
9818 | 0 | if (value_eq(&inner->as.array.elems[i], &args[0])) |
9819 | 0 | return eval_ok(value_bool(true)); |
9820 | 0 | } |
9821 | 0 | return eval_ok(value_bool(false)); |
9822 | 0 | } |
9823 | 0 | } |
9824 | | |
9825 | 0 | char *rerr = NULL; |
9826 | 0 | (void)asprintf(&rerr, "Ref has no method '%s'", method); |
9827 | 0 | return eval_err(rerr); |
9828 | 0 | } |
9829 | | |
9830 | 0 | char *err = NULL; |
9831 | 0 | (void)asprintf(&err, "unknown method '.%s()' on %s", method, value_type_name(&obj)); |
9832 | 0 | return eval_err(err); |
9833 | 10 | } |
9834 | | |
9835 | | /* ── Evaluator lifecycle ── */ |
9836 | | |
9837 | 865 | Evaluator *evaluator_new(void) { |
9838 | 865 | Evaluator *ev = calloc(1, sizeof(Evaluator)); |
9839 | 865 | ev->env = env_new(); |
9840 | 865 | ev->mode = MODE_CASUAL; |
9841 | 865 | ev->struct_defs = lat_map_new(sizeof(StructDecl *)); |
9842 | 865 | ev->enum_defs = lat_map_new(sizeof(EnumDecl *)); |
9843 | 865 | ev->fn_defs = lat_map_new(sizeof(FnDecl *)); |
9844 | 865 | ev->trait_defs = lat_map_new(sizeof(TraitDecl *)); |
9845 | 865 | ev->impl_registry = lat_map_new(sizeof(ImplBlock *)); |
9846 | 865 | stats_init(&ev->stats); |
9847 | 865 | ev->heap = dual_heap_new(); |
9848 | 865 | ev->gc_roots = lat_vec_new(sizeof(LatValue *)); |
9849 | 865 | ev->saved_envs = lat_vec_new(sizeof(Env *)); |
9850 | 865 | ev->gc_stress = false; |
9851 | 865 | ev->no_regions = false; |
9852 | 865 | ev->required_files = lat_map_new(sizeof(bool)); |
9853 | 865 | ev->module_cache = lat_map_new(sizeof(LatValue)); |
9854 | 865 | ev->loaded_extensions = lat_map_new(sizeof(LatValue)); |
9855 | 865 | ev->module_exprs = lat_vec_new(sizeof(Expr *)); |
9856 | 865 | ev->bonds = NULL; |
9857 | 865 | ev->bond_count = 0; |
9858 | 865 | ev->bond_cap = 0; |
9859 | 865 | ev->tracked_vars = NULL; |
9860 | 865 | ev->tracked_count = 0; |
9861 | 865 | ev->tracked_cap = 0; |
9862 | 865 | ev->reactions = NULL; |
9863 | 865 | ev->reaction_count = 0; |
9864 | 865 | ev->reaction_cap = 0; |
9865 | 865 | ev->defer_stack = NULL; |
9866 | 865 | ev->defer_count = 0; |
9867 | 865 | ev->defer_cap = 0; |
9868 | 865 | ev->assertions_enabled = true; |
9869 | 865 | value_set_heap(ev->heap); |
9870 | 865 | return ev; |
9871 | 865 | } |
9872 | | |
9873 | 865 | void evaluator_free(Evaluator *ev) { |
9874 | 865 | if (!ev) return; |
9875 | 865 | net_tls_cleanup(); |
9876 | 865 | env_free(ev->env); /* lat_free → fluid_dealloc removes from heap */ |
9877 | 865 | lat_map_free(&ev->struct_defs); |
9878 | 865 | lat_map_free(&ev->enum_defs); |
9879 | 865 | lat_map_free(&ev->fn_defs); |
9880 | 865 | lat_map_free(&ev->trait_defs); |
9881 | 865 | lat_map_free(&ev->impl_registry); |
9882 | 865 | lat_map_free(&ev->required_files); |
9883 | | /* Free cached module maps */ |
9884 | 14.7k | for (size_t i = 0; i < ev->module_cache.cap; i++) { |
9885 | 13.8k | if (ev->module_cache.entries[i].state == MAP_OCCUPIED) { |
9886 | 81 | LatValue *mv = (LatValue *)ev->module_cache.entries[i].value; |
9887 | 81 | value_free(mv); |
9888 | 81 | } |
9889 | 13.8k | } |
9890 | 865 | lat_map_free(&ev->module_cache); |
9891 | | /* Free cached extension maps */ |
9892 | 14.7k | for (size_t i = 0; i < ev->loaded_extensions.cap; i++) { |
9893 | 13.8k | if (ev->loaded_extensions.entries[i].state == MAP_OCCUPIED) { |
9894 | 15 | LatValue *mv = (LatValue *)ev->loaded_extensions.entries[i].value; |
9895 | 15 | value_free(mv); |
9896 | 15 | } |
9897 | 13.8k | } |
9898 | 865 | lat_map_free(&ev->loaded_extensions); |
9899 | | /* Free body Expr wrappers kept alive for module closures */ |
9900 | 4.26k | for (size_t i = 0; i < ev->module_exprs.len; i++) { |
9901 | 3.39k | Expr **ep = lat_vec_get(&ev->module_exprs, i); |
9902 | | /* Only free the wrapper node, not the stmts it borrows */ |
9903 | 3.39k | free(*ep); |
9904 | 3.39k | } |
9905 | 865 | lat_vec_free(&ev->module_exprs); |
9906 | 865 | free(ev->script_dir); |
9907 | | /* Free bonds */ |
9908 | 866 | for (size_t i = 0; i < ev->bond_count; i++) { |
9909 | 1 | free(ev->bonds[i].target); |
9910 | 2 | for (size_t j = 0; j < ev->bonds[i].dep_count; j++) { |
9911 | 1 | free(ev->bonds[i].deps[j]); |
9912 | 1 | if (ev->bonds[i].dep_strategies) free(ev->bonds[i].dep_strategies[j]); |
9913 | 1 | } |
9914 | 1 | free(ev->bonds[i].deps); |
9915 | 1 | free(ev->bonds[i].dep_strategies); |
9916 | 1 | } |
9917 | 865 | free(ev->bonds); |
9918 | | /* Free tracked variable histories */ |
9919 | 878 | for (size_t i = 0; i < ev->tracked_count; i++) { |
9920 | 13 | free(ev->tracked_vars[i].name); |
9921 | 43 | for (size_t j = 0; j < ev->tracked_vars[i].history.count; j++) { |
9922 | 30 | free(ev->tracked_vars[i].history.snapshots[j].phase_name); |
9923 | 30 | free(ev->tracked_vars[i].history.snapshots[j].fn_name); |
9924 | 30 | value_free(&ev->tracked_vars[i].history.snapshots[j].value); |
9925 | 30 | } |
9926 | 13 | free(ev->tracked_vars[i].history.snapshots); |
9927 | 13 | } |
9928 | 865 | free(ev->tracked_vars); |
9929 | | /* Free phase reactions */ |
9930 | 872 | for (size_t i = 0; i < ev->reaction_count; i++) { |
9931 | 7 | free(ev->reactions[i].var_name); |
9932 | 15 | for (size_t j = 0; j < ev->reactions[i].cb_count; j++) |
9933 | 8 | value_free(&ev->reactions[i].callbacks[j]); |
9934 | 7 | free(ev->reactions[i].callbacks); |
9935 | 7 | } |
9936 | 865 | free(ev->reactions); |
9937 | | /* Free seed crystals */ |
9938 | 867 | for (size_t i = 0; i < ev->seed_count; i++) { |
9939 | 2 | free(ev->seeds[i].var_name); |
9940 | 2 | value_free(&ev->seeds[i].contract); |
9941 | 2 | } |
9942 | 865 | free(ev->seeds); |
9943 | | /* Free pressure entries */ |
9944 | 870 | for (size_t i = 0; i < ev->pressure_count; i++) { |
9945 | 5 | free(ev->pressures[i].var_name); |
9946 | 5 | free(ev->pressures[i].mode); |
9947 | 5 | } |
9948 | 865 | free(ev->pressures); |
9949 | 865 | free(ev->defer_stack); |
9950 | 865 | free(ev->call_stack); |
9951 | 865 | value_set_heap(NULL); /* disconnect before freeing the heap itself */ |
9952 | 865 | dual_heap_free(ev->heap); /* frees any remaining tracked allocs */ |
9953 | 865 | lat_vec_free(&ev->gc_roots); |
9954 | 865 | lat_vec_free(&ev->saved_envs); |
9955 | 865 | free(ev); |
9956 | 865 | } |
9957 | | |
9958 | 26 | void evaluator_set_gc_stress(Evaluator *ev, bool enabled) { |
9959 | 26 | ev->gc_stress = enabled; |
9960 | 26 | } |
9961 | | |
9962 | 0 | void evaluator_set_no_regions(Evaluator *ev, bool enabled) { |
9963 | 0 | ev->no_regions = enabled; |
9964 | 0 | } |
9965 | | |
9966 | 0 | void evaluator_set_script_dir(Evaluator *ev, const char *dir) { |
9967 | 0 | free(ev->script_dir); |
9968 | 0 | ev->script_dir = dir ? strdup(dir) : NULL; |
9969 | 0 | } |
9970 | | |
9971 | 0 | void evaluator_set_argv(Evaluator *ev, int argc, char **argv) { |
9972 | 0 | ev->prog_argc = argc; |
9973 | 0 | ev->prog_argv = argv; |
9974 | 0 | } |
9975 | | |
9976 | 0 | void evaluator_set_assertions(Evaluator *ev, bool enabled) { |
9977 | 0 | ev->assertions_enabled = enabled; |
9978 | 0 | } |
9979 | | |
9980 | 865 | char *evaluator_run(Evaluator *ev, const Program *prog) { |
9981 | 865 | ev->mode = prog->mode; |
9982 | | |
9983 | | /* First pass: register structs, enums, functions, traits, and impls */ |
9984 | 2.07k | for (size_t i = 0; i < prog->item_count; i++) { |
9985 | 1.20k | if (prog->items[i].tag == ITEM_STRUCT) { |
9986 | 44 | StructDecl *ptr = &prog->items[i].as.struct_decl; |
9987 | 44 | lat_map_set(&ev->struct_defs, ptr->name, &ptr); |
9988 | 1.16k | } else if (prog->items[i].tag == ITEM_ENUM) { |
9989 | 9 | EnumDecl *ptr = &prog->items[i].as.enum_decl; |
9990 | 9 | lat_map_set(&ev->enum_defs, ptr->name, &ptr); |
9991 | 1.15k | } else if (prog->items[i].tag == ITEM_FUNCTION) { |
9992 | 905 | FnDecl *ptr = &prog->items[i].as.fn_decl; |
9993 | 905 | register_fn_overload(&ev->fn_defs, ptr); |
9994 | 905 | } else if (prog->items[i].tag == ITEM_TRAIT) { |
9995 | 4 | TraitDecl *ptr = &prog->items[i].as.trait_decl; |
9996 | 4 | lat_map_set(&ev->trait_defs, ptr->name, &ptr); |
9997 | 245 | } else if (prog->items[i].tag == ITEM_IMPL) { |
9998 | 5 | ImplBlock *ptr = &prog->items[i].as.impl_block; |
9999 | 5 | char key[512]; |
10000 | 5 | snprintf(key, sizeof(key), "%s::%s", ptr->type_name, ptr->trait_name); |
10001 | 5 | lat_map_set(&ev->impl_registry, key, &ptr); |
10002 | 5 | } |
10003 | 1.20k | } |
10004 | | |
10005 | | /* Second pass: execute top-level statements */ |
10006 | 2.06k | for (size_t i = 0; i < prog->item_count; i++) { |
10007 | 1.20k | if (prog->items[i].tag == ITEM_STMT) { |
10008 | 238 | EvalResult r = eval_stmt(ev, prog->items[i].as.stmt); |
10009 | 238 | if (IS_ERR(r)) { |
10010 | 4 | r.error = ev_attach_trace(ev, r.error); |
10011 | 4 | ev->call_depth = 0; |
10012 | 4 | return r.error; |
10013 | 4 | } |
10014 | 234 | if (IS_SIGNAL(r)) return strdup("unexpected control flow at top level"); |
10015 | 234 | value_free(&r.value); |
10016 | 234 | } |
10017 | 1.20k | } |
10018 | | |
10019 | | /* If there is a main() function, call it */ |
10020 | 861 | FnDecl *main_fn = find_fn(ev, "main"); |
10021 | 861 | if (main_fn) { |
10022 | 834 | EvalResult r = call_fn(ev, main_fn, NULL, 0, NULL); |
10023 | 834 | if (IS_ERR(r)) { |
10024 | 92 | r.error = ev_attach_trace(ev, r.error); |
10025 | 92 | ev->call_depth = 0; |
10026 | 92 | return r.error; |
10027 | 92 | } |
10028 | 742 | value_free(&r.value); |
10029 | 742 | } |
10030 | | |
10031 | 769 | return NULL; /* success */ |
10032 | 861 | } |
10033 | | |
10034 | 0 | int evaluator_run_tests(Evaluator *ev, const Program *prog) { |
10035 | 0 | ev->mode = prog->mode; |
10036 | | |
10037 | | /* First pass: register structs, enums, functions, traits, and impls */ |
10038 | 0 | for (size_t i = 0; i < prog->item_count; i++) { |
10039 | 0 | if (prog->items[i].tag == ITEM_STRUCT) { |
10040 | 0 | StructDecl *ptr = &prog->items[i].as.struct_decl; |
10041 | 0 | lat_map_set(&ev->struct_defs, ptr->name, &ptr); |
10042 | 0 | } else if (prog->items[i].tag == ITEM_ENUM) { |
10043 | 0 | EnumDecl *ptr = &prog->items[i].as.enum_decl; |
10044 | 0 | lat_map_set(&ev->enum_defs, ptr->name, &ptr); |
10045 | 0 | } else if (prog->items[i].tag == ITEM_FUNCTION) { |
10046 | 0 | FnDecl *ptr = &prog->items[i].as.fn_decl; |
10047 | 0 | register_fn_overload(&ev->fn_defs, ptr); |
10048 | 0 | } else if (prog->items[i].tag == ITEM_TRAIT) { |
10049 | 0 | TraitDecl *ptr = &prog->items[i].as.trait_decl; |
10050 | 0 | lat_map_set(&ev->trait_defs, ptr->name, &ptr); |
10051 | 0 | } else if (prog->items[i].tag == ITEM_IMPL) { |
10052 | 0 | ImplBlock *ptr = &prog->items[i].as.impl_block; |
10053 | 0 | char key[512]; |
10054 | 0 | snprintf(key, sizeof(key), "%s::%s", ptr->type_name, ptr->trait_name); |
10055 | 0 | lat_map_set(&ev->impl_registry, key, &ptr); |
10056 | 0 | } |
10057 | 0 | } |
10058 | | |
10059 | | /* Second pass: execute top-level statements (setup code) */ |
10060 | 0 | for (size_t i = 0; i < prog->item_count; i++) { |
10061 | 0 | if (prog->items[i].tag == ITEM_STMT) { |
10062 | 0 | EvalResult r = eval_stmt(ev, prog->items[i].as.stmt); |
10063 | 0 | if (IS_ERR(r)) { |
10064 | 0 | fprintf(stderr, "setup error: %s\n", r.error); |
10065 | 0 | free(r.error); |
10066 | 0 | return 1; |
10067 | 0 | } |
10068 | 0 | if (IS_SIGNAL(r)) { |
10069 | 0 | fprintf(stderr, "setup error: unexpected control flow at top level\n"); |
10070 | 0 | return 1; |
10071 | 0 | } |
10072 | 0 | value_free(&r.value); |
10073 | 0 | } |
10074 | 0 | } |
10075 | | |
10076 | | /* Count tests */ |
10077 | 0 | size_t test_count = 0; |
10078 | 0 | for (size_t i = 0; i < prog->item_count; i++) { |
10079 | 0 | if (prog->items[i].tag == ITEM_TEST) |
10080 | 0 | test_count++; |
10081 | 0 | } |
10082 | |
|
10083 | 0 | if (test_count == 0) { |
10084 | 0 | printf("No tests found.\n"); |
10085 | 0 | return 0; |
10086 | 0 | } |
10087 | | |
10088 | 0 | printf("Running %zu test%s...\n\n", test_count, test_count == 1 ? "" : "s"); |
10089 | | |
10090 | | /* Third pass: run tests */ |
10091 | 0 | size_t passed = 0, failed = 0; |
10092 | 0 | for (size_t i = 0; i < prog->item_count; i++) { |
10093 | 0 | if (prog->items[i].tag != ITEM_TEST) continue; |
10094 | 0 | TestDecl *td = &prog->items[i].as.test_decl; |
10095 | |
|
10096 | 0 | stats_scope_push(&ev->stats); |
10097 | 0 | env_push_scope(ev->env); |
10098 | |
|
10099 | 0 | bool ok = true; |
10100 | 0 | char *errmsg = NULL; |
10101 | 0 | for (size_t j = 0; j < td->body_count; j++) { |
10102 | 0 | EvalResult r = eval_stmt(ev, td->body[j]); |
10103 | 0 | if (IS_ERR(r)) { |
10104 | 0 | ok = false; |
10105 | 0 | errmsg = ev_attach_trace(ev, r.error); |
10106 | 0 | ev->call_depth = 0; |
10107 | 0 | break; |
10108 | 0 | } |
10109 | 0 | if (IS_SIGNAL(r)) { |
10110 | 0 | ok = false; |
10111 | 0 | errmsg = strdup("unexpected control flow in test"); |
10112 | 0 | break; |
10113 | 0 | } |
10114 | 0 | value_free(&r.value); |
10115 | 0 | } |
10116 | |
|
10117 | 0 | env_pop_scope(ev->env); |
10118 | 0 | stats_scope_pop(&ev->stats); |
10119 | |
|
10120 | 0 | if (ok) { |
10121 | 0 | passed++; |
10122 | 0 | printf(" ok: %s\n", td->name); |
10123 | 0 | } else { |
10124 | 0 | failed++; |
10125 | 0 | printf(" FAIL: %s\n", td->name); |
10126 | 0 | if (errmsg) { |
10127 | 0 | printf(" %s\n", errmsg); |
10128 | 0 | free(errmsg); |
10129 | 0 | } |
10130 | 0 | } |
10131 | 0 | } |
10132 | |
|
10133 | 0 | printf("\nResults: %zu passed, %zu failed, %zu total\n", passed, failed, test_count); |
10134 | 0 | return failed > 0 ? 1 : 0; |
10135 | 0 | } |
10136 | | |
10137 | 0 | char *evaluator_run_repl(Evaluator *ev, const Program *prog) { |
10138 | 0 | ev->mode = prog->mode; |
10139 | | |
10140 | | /* First pass: register structs, enums, functions, traits, and impls */ |
10141 | 0 | for (size_t i = 0; i < prog->item_count; i++) { |
10142 | 0 | if (prog->items[i].tag == ITEM_STRUCT) { |
10143 | 0 | StructDecl *ptr = &prog->items[i].as.struct_decl; |
10144 | 0 | lat_map_set(&ev->struct_defs, ptr->name, &ptr); |
10145 | 0 | } else if (prog->items[i].tag == ITEM_ENUM) { |
10146 | 0 | EnumDecl *ptr = &prog->items[i].as.enum_decl; |
10147 | 0 | lat_map_set(&ev->enum_defs, ptr->name, &ptr); |
10148 | 0 | } else if (prog->items[i].tag == ITEM_FUNCTION) { |
10149 | 0 | FnDecl *ptr = &prog->items[i].as.fn_decl; |
10150 | 0 | register_fn_overload(&ev->fn_defs, ptr); |
10151 | 0 | } else if (prog->items[i].tag == ITEM_TRAIT) { |
10152 | 0 | TraitDecl *ptr = &prog->items[i].as.trait_decl; |
10153 | 0 | lat_map_set(&ev->trait_defs, ptr->name, &ptr); |
10154 | 0 | } else if (prog->items[i].tag == ITEM_IMPL) { |
10155 | 0 | ImplBlock *ptr = &prog->items[i].as.impl_block; |
10156 | 0 | char key[512]; |
10157 | 0 | snprintf(key, sizeof(key), "%s::%s", ptr->type_name, ptr->trait_name); |
10158 | 0 | lat_map_set(&ev->impl_registry, key, &ptr); |
10159 | 0 | } |
10160 | 0 | } |
10161 | | |
10162 | | /* Second pass: execute top-level statements (no auto-main) */ |
10163 | 0 | for (size_t i = 0; i < prog->item_count; i++) { |
10164 | 0 | if (prog->items[i].tag == ITEM_STMT) { |
10165 | 0 | EvalResult r = eval_stmt(ev, prog->items[i].as.stmt); |
10166 | 0 | if (IS_ERR(r)) { |
10167 | 0 | r.error = ev_attach_trace(ev, r.error); |
10168 | 0 | ev->call_depth = 0; |
10169 | 0 | return r.error; |
10170 | 0 | } |
10171 | 0 | if (IS_SIGNAL(r)) return strdup("unexpected control flow at top level"); |
10172 | 0 | value_free(&r.value); |
10173 | 0 | } |
10174 | 0 | } |
10175 | | |
10176 | 0 | return NULL; /* success */ |
10177 | 0 | } |
10178 | | |
10179 | 0 | EvalResult evaluator_run_repl_result(Evaluator *ev, const Program *prog) { |
10180 | 0 | ev->mode = prog->mode; |
10181 | | |
10182 | | /* First pass: register structs, enums, functions, traits, and impls */ |
10183 | 0 | for (size_t i = 0; i < prog->item_count; i++) { |
10184 | 0 | if (prog->items[i].tag == ITEM_STRUCT) { |
10185 | 0 | StructDecl *ptr = &prog->items[i].as.struct_decl; |
10186 | 0 | lat_map_set(&ev->struct_defs, ptr->name, &ptr); |
10187 | 0 | } else if (prog->items[i].tag == ITEM_ENUM) { |
10188 | 0 | EnumDecl *ptr = &prog->items[i].as.enum_decl; |
10189 | 0 | lat_map_set(&ev->enum_defs, ptr->name, &ptr); |
10190 | 0 | } else if (prog->items[i].tag == ITEM_FUNCTION) { |
10191 | 0 | FnDecl *ptr = &prog->items[i].as.fn_decl; |
10192 | 0 | register_fn_overload(&ev->fn_defs, ptr); |
10193 | 0 | } else if (prog->items[i].tag == ITEM_TRAIT) { |
10194 | 0 | TraitDecl *ptr = &prog->items[i].as.trait_decl; |
10195 | 0 | lat_map_set(&ev->trait_defs, ptr->name, &ptr); |
10196 | 0 | } else if (prog->items[i].tag == ITEM_IMPL) { |
10197 | 0 | ImplBlock *ptr = &prog->items[i].as.impl_block; |
10198 | 0 | char key[512]; |
10199 | 0 | snprintf(key, sizeof(key), "%s::%s", ptr->type_name, ptr->trait_name); |
10200 | 0 | lat_map_set(&ev->impl_registry, key, &ptr); |
10201 | 0 | } |
10202 | 0 | } |
10203 | | |
10204 | | /* Second pass: execute top-level statements, keep last result */ |
10205 | 0 | LatValue last = value_unit(); |
10206 | 0 | for (size_t i = 0; i < prog->item_count; i++) { |
10207 | 0 | if (prog->items[i].tag == ITEM_STMT) { |
10208 | 0 | EvalResult r = eval_stmt(ev, prog->items[i].as.stmt); |
10209 | 0 | if (IS_ERR(r)) { |
10210 | 0 | r.error = ev_attach_trace(ev, r.error); |
10211 | 0 | ev->call_depth = 0; |
10212 | 0 | return r; |
10213 | 0 | } |
10214 | 0 | if (IS_SIGNAL(r)) { |
10215 | 0 | value_free(&last); |
10216 | 0 | return eval_err(strdup("unexpected control flow at top level")); |
10217 | 0 | } |
10218 | 0 | value_free(&last); |
10219 | 0 | last = r.value; |
10220 | 0 | } |
10221 | 0 | } |
10222 | | |
10223 | 0 | return eval_ok(last); |
10224 | 0 | } |
10225 | | |
10226 | 12 | char *eval_repr(Evaluator *ev, const LatValue *v) { |
10227 | 12 | if (v->type == VAL_STRUCT) { |
10228 | | /* Look for a "repr" closure field */ |
10229 | 8 | for (size_t i = 0; i < v->as.strct.field_count; i++) { |
10230 | 7 | if (strcmp(v->as.strct.field_names[i], "repr") == 0 && |
10231 | 7 | v->as.strct.field_values[i].type == VAL_CLOSURE) { |
10232 | 2 | const LatValue *cl = &v->as.strct.field_values[i]; |
10233 | 2 | LatValue self_arg = value_deep_clone(v); |
10234 | 2 | EvalResult r = call_closure(ev, |
10235 | 2 | cl->as.closure.param_names, |
10236 | 2 | cl->as.closure.param_count, |
10237 | 2 | cl->as.closure.body, |
10238 | 2 | cl->as.closure.captured_env, |
10239 | 2 | &self_arg, 1, |
10240 | 2 | cl->as.closure.default_values, |
10241 | 2 | cl->as.closure.has_variadic); |
10242 | 2 | if (IS_OK(r) && r.value.type == VAL_STR) { |
10243 | 1 | char *result = strdup(r.value.as.str_val); |
10244 | 1 | value_free(&r.value); |
10245 | 1 | return result; |
10246 | 1 | } |
10247 | 1 | if (IS_OK(r)) value_free(&r.value); |
10248 | | /* Fall through to default */ |
10249 | 1 | break; |
10250 | 2 | } |
10251 | 7 | } |
10252 | 3 | } |
10253 | 11 | return value_repr(v); |
10254 | 12 | } |
10255 | | |
10256 | 21 | const MemoryStats *evaluator_stats(const Evaluator *ev) { |
10257 | | /* Finalize snapshot metrics from heap state */ |
10258 | 21 | MemoryStats *s = (MemoryStats *)&ev->stats; |
10259 | 21 | s->fluid_peak_bytes = ev->heap->fluid->peak_bytes; |
10260 | 21 | s->fluid_live_bytes = ev->heap->fluid->total_bytes; |
10261 | 21 | s->fluid_cumulative_bytes = ev->heap->fluid->cumulative_bytes; |
10262 | 21 | s->region_peak_count = ev->heap->regions->peak_count; |
10263 | 21 | s->region_live_count = ev->heap->regions->count; |
10264 | 21 | s->region_live_data_bytes = region_live_data_bytes(ev->heap->regions); |
10265 | 21 | s->region_cumulative_data_bytes = ev->heap->regions->cumulative_data_bytes; |
10266 | 21 | struct rusage ru; |
10267 | 21 | if (getrusage(RUSAGE_SELF, &ru) == 0) { |
10268 | | #ifdef __linux__ |
10269 | | s->rss_peak_kb = (size_t)ru.ru_maxrss; |
10270 | | #else |
10271 | 21 | s->rss_peak_kb = (size_t)ru.ru_maxrss / 1024; |
10272 | 21 | #endif |
10273 | 21 | } |
10274 | 21 | return &ev->stats; |
10275 | 21 | } |
10276 | | |
10277 | 0 | void memory_stats_print(const MemoryStats *s, FILE *out) { |
10278 | 0 | fprintf(out, "=== Memory Statistics ===\n\n"); |
10279 | 0 | fprintf(out, "Phase transitions:\n"); |
10280 | 0 | fprintf(out, " freezes: %zu\n", s->freezes); |
10281 | 0 | fprintf(out, " thaws: %zu\n", s->thaws); |
10282 | 0 | fprintf(out, " deep clones: %zu\n", s->deep_clones); |
10283 | 0 | fprintf(out, "\nAllocations:\n"); |
10284 | 0 | fprintf(out, " arrays: %zu\n", s->array_allocs); |
10285 | 0 | fprintf(out, " structs: %zu\n", s->struct_allocs); |
10286 | 0 | fprintf(out, " closures: %zu\n", s->closure_allocs); |
10287 | 0 | fprintf(out, " total: %zu\n", s->array_allocs + s->struct_allocs + s->closure_allocs); |
10288 | 0 | fprintf(out, "\nMemory footprint:\n"); |
10289 | 0 | fprintf(out, " fluid peak: %zu bytes (%.2f KB)\n", |
10290 | 0 | s->fluid_peak_bytes, (double)s->fluid_peak_bytes / 1024.0); |
10291 | 0 | fprintf(out, " fluid live: %zu bytes\n", s->fluid_live_bytes); |
10292 | 0 | fprintf(out, " fluid total: %zu bytes (%.2f KB)\n", |
10293 | 0 | s->fluid_cumulative_bytes, (double)s->fluid_cumulative_bytes / 1024.0); |
10294 | 0 | if (s->fluid_peak_bytes > 0) |
10295 | 0 | fprintf(out, " churn ratio: %.1fx\n", |
10296 | 0 | (double)s->fluid_cumulative_bytes / (double)s->fluid_peak_bytes); |
10297 | 0 | fprintf(out, " region peak: %zu\n", s->region_peak_count); |
10298 | 0 | fprintf(out, " region live: %zu (%zu bytes data)\n", |
10299 | 0 | s->region_live_count, s->region_live_data_bytes); |
10300 | 0 | fprintf(out, " region total: %zu bytes data\n", s->region_cumulative_data_bytes); |
10301 | 0 | if (s->rss_peak_kb > 0) |
10302 | 0 | fprintf(out, " RSS peak: %zu KB\n", s->rss_peak_kb); |
10303 | 0 | fprintf(out, "\nScope lifecycle:\n"); |
10304 | 0 | fprintf(out, " pushes: %zu\n", s->scope_pushes); |
10305 | 0 | fprintf(out, " pops: %zu\n", s->scope_pops); |
10306 | 0 | fprintf(out, " peak depth: %zu\n", s->peak_scope_depth); |
10307 | 0 | fprintf(out, "\nCalls & bindings:\n"); |
10308 | 0 | fprintf(out, " bindings: %zu\n", s->bindings_created); |
10309 | 0 | fprintf(out, " fn calls: %zu\n", s->fn_calls); |
10310 | 0 | fprintf(out, " closure calls:%zu\n", s->closure_calls); |
10311 | 0 | fprintf(out, "\nForge blocks: %zu\n", s->forge_blocks); |
10312 | 0 | fprintf(out, "\nGarbage collection:\n"); |
10313 | 0 | fprintf(out, " gc cycles: %zu\n", s->gc_cycles); |
10314 | 0 | fprintf(out, " swept fluid: %zu (%zu bytes)\n", s->gc_swept_fluid, s->gc_bytes_swept); |
10315 | 0 | fprintf(out, " swept regions:%zu\n", s->gc_swept_regions); |
10316 | 0 | if (s->gc_cycles > 0) |
10317 | 0 | fprintf(out, " avg/cycle: %.2f KB swept\n", |
10318 | 0 | (double)s->gc_bytes_swept / 1024.0 / (double)s->gc_cycles); |
10319 | 0 | fprintf(out, "\nTiming:\n"); |
10320 | 0 | fprintf(out, " gc total: %.3f ms\n", (double)s->gc_total_ns / 1e6); |
10321 | 0 | fprintf(out, " freeze total: %.3f ms\n", (double)s->freeze_total_ns / 1e6); |
10322 | 0 | fprintf(out, " thaw total: %.3f ms\n", (double)s->thaw_total_ns / 1e6); |
10323 | 0 | if (s->gc_cycles > 0) |
10324 | 0 | fprintf(out, " avg gc cycle: %.3f ms\n", |
10325 | 0 | (double)s->gc_total_ns / 1e6 / (double)s->gc_cycles); |
10326 | 0 | } |