/Users/alexjokela/projects/lattice/src/value.c
Line | Count | Source |
1 | | #include "value.h" |
2 | | #include "env.h" |
3 | | #include "memory.h" |
4 | | #include "channel.h" |
5 | | #include <stdlib.h> |
6 | | #include <string.h> |
7 | | #include <stdio.h> |
8 | | |
9 | | _Static_assert(sizeof(LatValue) <= LAT_MAP_INLINE_MAX, |
10 | | "LatValue must fit in LAT_MAP_INLINE_MAX bytes for inline hashmap storage"); |
11 | | |
12 | | /* ── Heap-tracked allocation wrappers ── */ |
13 | | |
14 | | #ifdef __EMSCRIPTEN__ |
15 | | static DualHeap *g_heap = NULL; |
16 | | static CrystalRegion *g_arena = NULL; |
17 | | #else |
18 | | static _Thread_local DualHeap *g_heap = NULL; |
19 | | static _Thread_local CrystalRegion *g_arena = NULL; |
20 | | #endif |
21 | | |
22 | 3.46k | void value_set_heap(DualHeap *heap) { g_heap = heap; } |
23 | 2.41k | void value_set_arena(CrystalRegion *region) { g_arena = region; } |
24 | 9.45k | CrystalRegion *value_get_arena(void) { return g_arena; } |
25 | | |
26 | 198k | static void *lat_alloc(size_t size) { |
27 | 198k | if (g_arena) return arena_alloc(g_arena, size); |
28 | 197k | if (g_heap) return fluid_alloc(g_heap->fluid, size); |
29 | 140k | return malloc(size); |
30 | 197k | } |
31 | | |
32 | 84 | static void *lat_calloc(size_t count, size_t size) { |
33 | 84 | if (count > 0 && size > SIZE_MAX / count) return NULL; |
34 | 84 | if (g_arena) return arena_calloc(g_arena, count, size); |
35 | 24 | if (g_heap) { |
36 | 8 | size_t total = count * size; |
37 | 8 | void *ptr = fluid_alloc(g_heap->fluid, total); |
38 | 8 | memset(ptr, 0, total); |
39 | 8 | return ptr; |
40 | 8 | } |
41 | 16 | return calloc(count, size); |
42 | 24 | } |
43 | | |
44 | 146k | static char *lat_strdup(const char *s) { |
45 | 146k | if (g_arena) return arena_strdup(g_arena, s); |
46 | 146k | size_t len = strlen(s) + 1; |
47 | 146k | char *p = lat_alloc(len); |
48 | 146k | memcpy(p, s, len); |
49 | 146k | return p; |
50 | 146k | } |
51 | | |
52 | 219k | static void lat_free(void *ptr) { |
53 | 219k | if (!ptr) return; |
54 | 219k | if (g_arena) return; /* no-op during arena clone */ |
55 | 219k | if (g_heap && fluid_dealloc(g_heap->fluid, ptr)) return; |
56 | 162k | free(ptr); |
57 | 162k | } |
58 | | |
59 | | /* ── Arena-routed allocation (public, for env.c) ── */ |
60 | | |
61 | 26 | void *lat_alloc_routed(size_t size) { return lat_alloc(size); } |
62 | 38 | void *lat_calloc_routed(size_t count, size_t size) { return lat_calloc(count, size); } |
63 | 19 | char *lat_strdup_routed(const char *s) { return lat_strdup(s); } |
64 | | |
65 | | /* ── Constructors ── */ |
66 | | |
67 | 34.4k | LatValue value_int(int64_t v) { |
68 | 34.4k | LatValue val = { .type = VAL_INT, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
69 | 34.4k | val.as.int_val = v; |
70 | 34.4k | return val; |
71 | 34.4k | } |
72 | | |
73 | 431 | LatValue value_float(double v) { |
74 | 431 | LatValue val = { .type = VAL_FLOAT, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
75 | 431 | val.as.float_val = v; |
76 | 431 | return val; |
77 | 431 | } |
78 | | |
79 | 9.60k | LatValue value_bool(bool v) { |
80 | 9.60k | LatValue val = { .type = VAL_BOOL, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
81 | 9.60k | val.as.bool_val = v; |
82 | 9.60k | return val; |
83 | 9.60k | } |
84 | | |
85 | 72.0k | LatValue value_string(const char *s) { |
86 | 72.0k | LatValue val = { .type = VAL_STR, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
87 | 72.0k | val.as.str_val = lat_strdup(s); |
88 | 72.0k | return val; |
89 | 72.0k | } |
90 | | |
91 | 1.35k | LatValue value_string_owned(char *s) { |
92 | 1.35k | LatValue val = { .type = VAL_STR, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
93 | 1.35k | val.as.str_val = s; |
94 | 1.35k | return val; |
95 | 1.35k | } |
96 | | |
97 | 4.82k | LatValue value_array(LatValue *elems, size_t len) { |
98 | 4.82k | LatValue val = { .type = VAL_ARRAY, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
99 | 4.82k | size_t cap = len < 4 ? 4 : len; |
100 | 4.82k | val.as.array.elems = lat_alloc(cap * sizeof(LatValue)); |
101 | 4.82k | if (len > 0) memcpy(val.as.array.elems, elems, len * sizeof(LatValue)); |
102 | 4.82k | val.as.array.len = len; |
103 | 4.82k | val.as.array.cap = cap; |
104 | 4.82k | return val; |
105 | 4.82k | } |
106 | | |
107 | 406 | LatValue value_struct(const char *name, char **field_names, LatValue *field_values, size_t count) { |
108 | 406 | LatValue val = { .type = VAL_STRUCT, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
109 | 406 | val.as.strct.name = lat_strdup(name); |
110 | 406 | val.as.strct.field_names = lat_alloc(count * sizeof(char *)); |
111 | 406 | val.as.strct.field_values = lat_alloc(count * sizeof(LatValue)); |
112 | 406 | val.as.strct.field_phases = NULL; /* lazy-allocated on first field freeze */ |
113 | 1.21k | for (size_t i = 0; i < count; i++) { |
114 | 812 | val.as.strct.field_names[i] = lat_strdup(field_names[i]); |
115 | 812 | val.as.strct.field_values[i] = field_values[i]; |
116 | 812 | } |
117 | 406 | val.as.strct.field_count = count; |
118 | 406 | return val; |
119 | 406 | } |
120 | | |
121 | 194 | LatValue value_struct_vm(const char *name, const char **field_names, LatValue *field_values, size_t count) { |
122 | 194 | LatValue val = { .type = VAL_STRUCT, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
123 | 194 | val.as.strct.name = lat_strdup(name); |
124 | 194 | val.as.strct.field_names = lat_alloc(count * sizeof(char *)); |
125 | 194 | val.as.strct.field_values = lat_alloc(count * sizeof(LatValue)); |
126 | 194 | val.as.strct.field_phases = NULL; |
127 | 578 | for (size_t i = 0; i < count; i++) { |
128 | 384 | val.as.strct.field_names[i] = lat_strdup(field_names[i]); |
129 | 384 | val.as.strct.field_values[i] = field_values[i]; |
130 | 384 | } |
131 | 194 | val.as.strct.field_count = count; |
132 | 194 | return val; |
133 | 194 | } |
134 | | |
135 | | LatValue value_closure(char **param_names, size_t param_count, struct Expr *body, Env *captured, |
136 | 2.16k | struct Expr **default_values, bool has_variadic) { |
137 | 2.16k | LatValue val = { .type = VAL_CLOSURE, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
138 | 2.16k | val.as.closure.param_names = lat_alloc(param_count * sizeof(char *)); |
139 | 5.41k | for (size_t i = 0; i < param_count; i++) { |
140 | 3.24k | val.as.closure.param_names[i] = lat_strdup(param_names[i]); |
141 | 3.24k | } |
142 | 2.16k | val.as.closure.param_count = param_count; |
143 | 2.16k | val.as.closure.body = body; /* borrowed reference */ |
144 | 2.16k | val.as.closure.captured_env = captured; |
145 | 2.16k | val.as.closure.default_values = default_values; /* borrowed from AST */ |
146 | 2.16k | val.as.closure.has_variadic = has_variadic; |
147 | 2.16k | return val; |
148 | 2.16k | } |
149 | | |
150 | 39.5k | LatValue value_unit(void) { |
151 | 39.5k | return (LatValue){ .type = VAL_UNIT, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
152 | 39.5k | } |
153 | | |
154 | 14.3M | LatValue value_nil(void) { |
155 | 14.3M | return (LatValue){ .type = VAL_NIL, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
156 | 14.3M | } |
157 | | |
158 | 48 | LatValue value_range(int64_t start, int64_t end) { |
159 | 48 | LatValue val = { .type = VAL_RANGE, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
160 | 48 | val.as.range.start = start; |
161 | 48 | val.as.range.end = end; |
162 | 48 | return val; |
163 | 48 | } |
164 | | |
165 | 2.44k | LatValue value_map_new(void) { |
166 | 2.44k | LatValue val = { .type = VAL_MAP, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
167 | 2.44k | val.as.map.map = lat_alloc(sizeof(LatMap)); |
168 | 2.44k | *val.as.map.map = lat_map_new(sizeof(LatValue)); |
169 | 2.44k | val.as.map.key_phases = NULL; /* lazy-allocated on first key freeze */ |
170 | 2.44k | return val; |
171 | 2.44k | } |
172 | | |
173 | 42 | LatValue value_channel(struct LatChannel *ch) { |
174 | 42 | LatValue val = { .type = VAL_CHANNEL, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
175 | 42 | channel_retain(ch); |
176 | 42 | val.as.channel.ch = ch; |
177 | 42 | return val; |
178 | 42 | } |
179 | | |
180 | | LatValue value_enum(const char *enum_name, const char *variant_name, |
181 | 42 | LatValue *payload, size_t count) { |
182 | 42 | LatValue val = { .type = VAL_ENUM, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
183 | 42 | val.as.enm.enum_name = lat_strdup(enum_name); |
184 | 42 | val.as.enm.variant_name = lat_strdup(variant_name); |
185 | 42 | if (count > 0 && payload) { |
186 | 12 | val.as.enm.payload = lat_alloc(count * sizeof(LatValue)); |
187 | 30 | for (size_t i = 0; i < count; i++) |
188 | 18 | val.as.enm.payload[i] = value_deep_clone(&payload[i]); |
189 | 12 | val.as.enm.payload_count = count; |
190 | 30 | } else { |
191 | 30 | val.as.enm.payload = NULL; |
192 | 30 | val.as.enm.payload_count = 0; |
193 | 30 | } |
194 | 42 | return val; |
195 | 42 | } |
196 | | |
197 | 57 | LatValue value_set_new(void) { |
198 | 57 | LatValue val = { .type = VAL_SET, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
199 | 57 | val.as.set.map = lat_alloc(sizeof(LatMap)); |
200 | 57 | *val.as.set.map = lat_map_new(sizeof(LatValue)); |
201 | 57 | return val; |
202 | 57 | } |
203 | | |
204 | 22 | LatValue value_tuple(LatValue *elems, size_t len) { |
205 | 22 | LatValue val = { .type = VAL_TUPLE, .phase = VTAG_CRYSTAL, .region_id = (size_t)-1 }; |
206 | 22 | val.as.tuple.elems = lat_alloc(len * sizeof(LatValue)); |
207 | 84 | for (size_t i = 0; i < len; i++) { |
208 | 62 | val.as.tuple.elems[i] = value_deep_clone(&elems[i]); |
209 | 62 | } |
210 | 22 | val.as.tuple.len = len; |
211 | 22 | return val; |
212 | 22 | } |
213 | | |
214 | 33 | LatValue value_buffer(const uint8_t *data, size_t len) { |
215 | 33 | LatValue val = { .type = VAL_BUFFER, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
216 | 33 | size_t cap = len < 8 ? 8 : len; |
217 | 33 | val.as.buffer.data = lat_alloc(cap); |
218 | 33 | if (len > 0 && data) memcpy(val.as.buffer.data, data, len); |
219 | 33 | val.as.buffer.len = len; |
220 | 33 | val.as.buffer.cap = cap; |
221 | 33 | return val; |
222 | 33 | } |
223 | | |
224 | 24 | LatValue value_buffer_alloc(size_t size) { |
225 | 24 | LatValue val = { .type = VAL_BUFFER, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
226 | 24 | size_t cap = size < 8 ? 8 : size; |
227 | 24 | val.as.buffer.data = lat_calloc(cap, 1); |
228 | 24 | val.as.buffer.len = size; |
229 | 24 | val.as.buffer.cap = cap; |
230 | 24 | return val; |
231 | 24 | } |
232 | | |
233 | 45 | LatValue value_ref(LatValue inner) { |
234 | 45 | LatValue val = { .type = VAL_REF, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 }; |
235 | 45 | LatRef *r = malloc(sizeof(LatRef)); |
236 | 45 | r->value = value_deep_clone(&inner); |
237 | 45 | r->refcount = 1; |
238 | 45 | val.as.ref.ref = r; |
239 | 45 | return val; |
240 | 45 | } |
241 | | |
242 | 140 | void ref_retain(LatRef *r) { |
243 | 140 | if (r) r->refcount++; |
244 | 140 | } |
245 | | |
246 | 163 | void ref_release(LatRef *r) { |
247 | 163 | if (!r) return; |
248 | 163 | if (--r->refcount == 0) { |
249 | 33 | value_free(&r->value); |
250 | 33 | free(r); |
251 | 33 | } |
252 | 163 | } |
253 | | |
254 | | /* ── Phase helpers ── */ |
255 | | |
256 | 0 | bool value_is_fluid(const LatValue *v) { return v->phase == VTAG_FLUID; } |
257 | 304 | bool value_is_crystal(const LatValue *v) { return v->phase == VTAG_CRYSTAL; } |
258 | | |
259 | | /* ── Deep clone ── */ |
260 | | |
261 | 114k | LatValue value_deep_clone(const LatValue *v) { |
262 | 114k | LatValue out = { .type = v->type, .phase = v->phase, .region_id = (size_t)-1 }; |
263 | | |
264 | 114k | switch (v->type) { |
265 | 40.1k | case VAL_INT: out.as.int_val = v->as.int_val; break; |
266 | 387 | case VAL_FLOAT: out.as.float_val = v->as.float_val; break; |
267 | 5.04k | case VAL_BOOL: out.as.bool_val = v->as.bool_val; break; |
268 | 21.3k | case VAL_STR: out.as.str_val = lat_strdup(v->as.str_val); break; |
269 | 3.30k | case VAL_ARRAY: { |
270 | 3.30k | size_t len = v->as.array.len; |
271 | 3.30k | size_t cap = v->as.array.cap; |
272 | 3.30k | out.as.array.elems = lat_alloc(cap * sizeof(LatValue)); |
273 | 22.9k | for (size_t i = 0; i < len; i++) { |
274 | 19.5k | out.as.array.elems[i] = value_deep_clone(&v->as.array.elems[i]); |
275 | 19.5k | } |
276 | 3.30k | out.as.array.len = len; |
277 | 3.30k | out.as.array.cap = cap; |
278 | 3.30k | break; |
279 | 0 | } |
280 | 3.05k | case VAL_STRUCT: { |
281 | 3.05k | size_t fc = v->as.strct.field_count; |
282 | 3.05k | out.as.strct.name = lat_strdup(v->as.strct.name); |
283 | 3.05k | out.as.strct.field_names = lat_alloc(fc * sizeof(char *)); |
284 | 3.05k | out.as.strct.field_values = lat_alloc(fc * sizeof(LatValue)); |
285 | 9.19k | for (size_t i = 0; i < fc; i++) { |
286 | 6.13k | out.as.strct.field_names[i] = lat_strdup(v->as.strct.field_names[i]); |
287 | 6.13k | out.as.strct.field_values[i] = value_deep_clone(&v->as.strct.field_values[i]); |
288 | 6.13k | } |
289 | 3.05k | out.as.strct.field_count = fc; |
290 | 3.05k | if (v->as.strct.field_phases) { |
291 | 20 | out.as.strct.field_phases = lat_alloc(fc * sizeof(PhaseTag)); |
292 | 20 | memcpy(out.as.strct.field_phases, v->as.strct.field_phases, fc * sizeof(PhaseTag)); |
293 | 3.03k | } else { |
294 | 3.03k | out.as.strct.field_phases = NULL; |
295 | 3.03k | } |
296 | 3.05k | break; |
297 | 0 | } |
298 | 30.2k | case VAL_CLOSURE: { |
299 | 30.2k | size_t pc = v->as.closure.param_count; |
300 | 30.2k | if (v->as.closure.param_names) { |
301 | 24.4k | out.as.closure.param_names = lat_alloc(pc * sizeof(char *)); |
302 | 63.2k | for (size_t i = 0; i < pc; i++) { |
303 | 38.7k | out.as.closure.param_names[i] = lat_strdup(v->as.closure.param_names[i]); |
304 | 38.7k | } |
305 | 24.4k | } else { |
306 | 5.88k | out.as.closure.param_names = NULL; |
307 | 5.88k | } |
308 | 30.2k | out.as.closure.param_count = pc; |
309 | 30.2k | out.as.closure.body = v->as.closure.body; /* borrowed */ |
310 | | /* Compiled bytecode closures store ObjUpvalue** in captured_env (not Env*). |
311 | | * Shallow-copy the pointer; the VM manages upvalue lifetime. */ |
312 | 30.2k | if (v->as.closure.body == NULL && v->as.closure.native_fn != NULL) { |
313 | 22.7k | out.as.closure.captured_env = v->as.closure.captured_env; |
314 | 22.7k | } else { |
315 | | /* Tree-walk closures: share the env (refcounted) so mutations |
316 | | * inside the closure persist across calls. |
317 | | * Exception: when an arena is active (during freeze), we must |
318 | | * clone the env into the arena so GC can safely skip crystal |
319 | | * values without traversing their fluid-heap env pointers. */ |
320 | 7.53k | if (value_get_arena()) { |
321 | 13 | out.as.closure.captured_env = env_clone(v->as.closure.captured_env); |
322 | 7.52k | } else { |
323 | 7.52k | out.as.closure.captured_env = v->as.closure.captured_env; |
324 | 7.52k | env_retain(v->as.closure.captured_env); |
325 | 7.52k | } |
326 | 7.53k | } |
327 | 30.2k | out.as.closure.default_values = v->as.closure.default_values; /* borrowed */ |
328 | 30.2k | out.as.closure.has_variadic = v->as.closure.has_variadic; |
329 | 30.2k | out.as.closure.native_fn = v->as.closure.native_fn; /* shared, not owned */ |
330 | | /* Compiled bytecode closures store upvalue count in region_id */ |
331 | 30.2k | if (v->as.closure.body == NULL && v->as.closure.native_fn != NULL) |
332 | 22.7k | out.region_id = v->region_id; |
333 | 30.2k | break; |
334 | 0 | } |
335 | 110 | case VAL_UNIT: break; |
336 | 2.79k | case VAL_NIL: break; |
337 | 0 | case VAL_RANGE: |
338 | 0 | out.as.range.start = v->as.range.start; |
339 | 0 | out.as.range.end = v->as.range.end; |
340 | 0 | break; |
341 | 119 | case VAL_CHANNEL: |
342 | 119 | channel_retain(v->as.channel.ch); |
343 | 119 | out.as.channel.ch = v->as.channel.ch; |
344 | 119 | break; |
345 | 52 | case VAL_ENUM: { |
346 | 52 | out.as.enm.enum_name = lat_strdup(v->as.enm.enum_name); |
347 | 52 | out.as.enm.variant_name = lat_strdup(v->as.enm.variant_name); |
348 | 52 | if (v->as.enm.payload_count > 0) { |
349 | 8 | out.as.enm.payload = lat_alloc(v->as.enm.payload_count * sizeof(LatValue)); |
350 | 20 | for (size_t i = 0; i < v->as.enm.payload_count; i++) |
351 | 12 | out.as.enm.payload[i] = value_deep_clone(&v->as.enm.payload[i]); |
352 | 8 | out.as.enm.payload_count = v->as.enm.payload_count; |
353 | 44 | } else { |
354 | 44 | out.as.enm.payload = NULL; |
355 | 44 | out.as.enm.payload_count = 0; |
356 | 44 | } |
357 | 52 | break; |
358 | 0 | } |
359 | 7.41k | case VAL_MAP: { |
360 | 7.41k | LatMap *src = v->as.map.map; |
361 | 7.41k | if (g_arena) { |
362 | | /* Arena mode: build map internals through lat_alloc/lat_calloc |
363 | | * so everything goes into the arena. No rehashing possible. */ |
364 | 22 | LatMap *dst = lat_alloc(sizeof(LatMap)); |
365 | 22 | dst->value_size = src->value_size; |
366 | 22 | dst->cap = src->cap; |
367 | 22 | dst->count = src->count; /* preserve tombstone count for probe chains */ |
368 | 22 | dst->live = src->live; |
369 | 22 | dst->entries = lat_calloc(src->cap, sizeof(LatMapEntry)); |
370 | 374 | for (size_t i = 0; i < src->cap; i++) { |
371 | 352 | dst->entries[i].value = dst->entries[i]._ibuf; |
372 | 352 | if (src->entries[i].state == MAP_OCCUPIED) { |
373 | 15 | dst->entries[i].state = MAP_OCCUPIED; |
374 | 15 | dst->entries[i].key = lat_strdup(src->entries[i].key); |
375 | 15 | LatValue *sv = (LatValue *)src->entries[i].value; |
376 | 15 | LatValue cloned = value_deep_clone(sv); |
377 | 15 | *(LatValue *)dst->entries[i].value = cloned; |
378 | 337 | } else if (src->entries[i].state == MAP_TOMBSTONE) { |
379 | 0 | dst->entries[i].state = MAP_TOMBSTONE; |
380 | 0 | } |
381 | 352 | } |
382 | 22 | out.as.map.map = dst; |
383 | 7.39k | } else { |
384 | | /* Normal path: lat_map_new + lat_map_set */ |
385 | 7.39k | out.as.map.map = lat_alloc(sizeof(LatMap)); |
386 | 7.39k | *out.as.map.map = lat_map_new(sizeof(LatValue)); |
387 | 143k | for (size_t i = 0; i < src->cap; i++) { |
388 | 135k | if (src->entries[i].state == MAP_OCCUPIED) { |
389 | 32.5k | LatValue *sv = (LatValue *)src->entries[i].value; |
390 | 32.5k | LatValue cloned = value_deep_clone(sv); |
391 | 32.5k | lat_map_set(out.as.map.map, src->entries[i].key, &cloned); |
392 | 32.5k | } |
393 | 135k | } |
394 | 7.39k | } |
395 | | /* Clone per-key phases if present */ |
396 | 7.41k | if (v->as.map.key_phases) { |
397 | 10 | LatMap *ksrc = v->as.map.key_phases; |
398 | 10 | out.as.map.key_phases = lat_alloc(sizeof(LatMap)); |
399 | 10 | *out.as.map.key_phases = lat_map_new(sizeof(PhaseTag)); |
400 | 170 | for (size_t i = 0; i < ksrc->cap; i++) { |
401 | 160 | if (ksrc->entries[i].state == MAP_OCCUPIED) { |
402 | 18 | lat_map_set(out.as.map.key_phases, ksrc->entries[i].key, |
403 | 18 | ksrc->entries[i].value); |
404 | 18 | } |
405 | 160 | } |
406 | 7.40k | } else { |
407 | 7.40k | out.as.map.key_phases = NULL; |
408 | 7.40k | } |
409 | 7.41k | break; |
410 | 0 | } |
411 | 77 | case VAL_SET: { |
412 | 77 | LatMap *src = v->as.set.map; |
413 | 77 | if (g_arena) { |
414 | 0 | LatMap *dst = lat_alloc(sizeof(LatMap)); |
415 | 0 | dst->value_size = src->value_size; |
416 | 0 | dst->cap = src->cap; |
417 | 0 | dst->count = src->count; /* preserve tombstone count for probe chains */ |
418 | 0 | dst->live = src->live; |
419 | 0 | dst->entries = lat_calloc(src->cap, sizeof(LatMapEntry)); |
420 | 0 | for (size_t i = 0; i < src->cap; i++) { |
421 | 0 | dst->entries[i].value = dst->entries[i]._ibuf; |
422 | 0 | if (src->entries[i].state == MAP_OCCUPIED) { |
423 | 0 | dst->entries[i].state = MAP_OCCUPIED; |
424 | 0 | dst->entries[i].key = lat_strdup(src->entries[i].key); |
425 | 0 | LatValue *sv = (LatValue *)src->entries[i].value; |
426 | 0 | LatValue cloned = value_deep_clone(sv); |
427 | 0 | *(LatValue *)dst->entries[i].value = cloned; |
428 | 0 | } else if (src->entries[i].state == MAP_TOMBSTONE) { |
429 | 0 | dst->entries[i].state = MAP_TOMBSTONE; |
430 | 0 | } |
431 | 0 | } |
432 | 0 | out.as.set.map = dst; |
433 | 77 | } else { |
434 | 77 | out.as.set.map = lat_alloc(sizeof(LatMap)); |
435 | 77 | *out.as.set.map = lat_map_new(sizeof(LatValue)); |
436 | 1.30k | for (size_t i = 0; i < src->cap; i++) { |
437 | 1.23k | if (src->entries[i].state == MAP_OCCUPIED) { |
438 | 140 | LatValue *sv = (LatValue *)src->entries[i].value; |
439 | 140 | LatValue cloned = value_deep_clone(sv); |
440 | 140 | lat_map_set(out.as.set.map, src->entries[i].key, &cloned); |
441 | 140 | } |
442 | 1.23k | } |
443 | 77 | } |
444 | 77 | break; |
445 | 0 | } |
446 | 33 | case VAL_TUPLE: { |
447 | 33 | out.as.tuple.elems = lat_alloc(v->as.tuple.len * sizeof(LatValue)); |
448 | 118 | for (size_t i = 0; i < v->as.tuple.len; i++) { |
449 | 85 | out.as.tuple.elems[i] = value_deep_clone(&v->as.tuple.elems[i]); |
450 | 85 | } |
451 | 33 | out.as.tuple.len = v->as.tuple.len; |
452 | 33 | break; |
453 | 0 | } |
454 | 185 | case VAL_BUFFER: { |
455 | 185 | size_t cap = v->as.buffer.cap; |
456 | 185 | out.as.buffer.data = lat_alloc(cap); |
457 | 185 | memcpy(out.as.buffer.data, v->as.buffer.data, v->as.buffer.len); |
458 | 185 | out.as.buffer.len = v->as.buffer.len; |
459 | 185 | out.as.buffer.cap = cap; |
460 | 185 | break; |
461 | 0 | } |
462 | 114 | case VAL_REF: |
463 | 114 | ref_retain(v->as.ref.ref); |
464 | 114 | out.as.ref.ref = v->as.ref.ref; |
465 | 114 | break; |
466 | 114k | } |
467 | 114k | return out; |
468 | 114k | } |
469 | | |
470 | | /* ── Freeze ── */ |
471 | | |
472 | 3.84k | static void set_phase_recursive(LatValue *v, PhaseTag phase) { |
473 | 3.84k | v->phase = phase; |
474 | 3.84k | if (v->type == VAL_ARRAY) { |
475 | 638 | for (size_t i = 0; i < v->as.array.len; i++) { |
476 | 468 | set_phase_recursive(&v->as.array.elems[i], phase); |
477 | 468 | } |
478 | 3.67k | } else if (v->type == VAL_STRUCT) { |
479 | 2.80k | for (size_t i = 0; i < v->as.strct.field_count; i++) { |
480 | 1.87k | set_phase_recursive(&v->as.strct.field_values[i], phase); |
481 | 1.87k | } |
482 | | /* Update field-level phases */ |
483 | 932 | if (v->as.strct.field_phases) { |
484 | 0 | if (phase == VTAG_CRYSTAL) { |
485 | 0 | for (size_t i = 0; i < v->as.strct.field_count; i++) |
486 | 0 | v->as.strct.field_phases[i] = VTAG_CRYSTAL; |
487 | 0 | } else { |
488 | | /* Thawing: clear per-field phases */ |
489 | 0 | lat_free(v->as.strct.field_phases); |
490 | 0 | v->as.strct.field_phases = NULL; |
491 | 0 | } |
492 | 0 | } |
493 | 2.74k | } else if (v->type == VAL_MAP) { |
494 | 1.34k | for (size_t i = 0; i < v->as.map.map->cap; i++) { |
495 | 1.26k | if (v->as.map.map->entries[i].state == MAP_OCCUPIED) { |
496 | 63 | LatValue *mv = (LatValue *)v->as.map.map->entries[i].value; |
497 | 63 | set_phase_recursive(mv, phase); |
498 | 63 | } |
499 | 1.26k | } |
500 | 2.66k | } else if (v->type == VAL_ENUM) { |
501 | 0 | for (size_t i = 0; i < v->as.enm.payload_count; i++) |
502 | 0 | set_phase_recursive(&v->as.enm.payload[i], phase); |
503 | 2.66k | } else if (v->type == VAL_SET) { |
504 | 0 | for (size_t i = 0; i < v->as.set.map->cap; i++) { |
505 | 0 | if (v->as.set.map->entries[i].state == MAP_OCCUPIED) { |
506 | 0 | LatValue *sv = (LatValue *)v->as.set.map->entries[i].value; |
507 | 0 | set_phase_recursive(sv, phase); |
508 | 0 | } |
509 | 0 | } |
510 | 2.66k | } else if (v->type == VAL_TUPLE) { |
511 | 0 | for (size_t i = 0; i < v->as.tuple.len; i++) { |
512 | 0 | set_phase_recursive(&v->as.tuple.elems[i], phase); |
513 | 0 | } |
514 | 0 | } |
515 | | /* VAL_BUFFER: just set phase tag (no nested values) */ |
516 | 2.66k | else if (v->type == VAL_REF) { |
517 | 3 | set_phase_recursive(&v->as.ref.ref->value, phase); |
518 | 3 | } |
519 | 3.84k | } |
520 | | |
521 | 894 | LatValue value_freeze(LatValue v) { |
522 | 894 | set_phase_recursive(&v, VTAG_CRYSTAL); |
523 | 894 | return v; |
524 | 894 | } |
525 | | |
526 | | /* ── Thaw ── */ |
527 | | |
528 | 543 | LatValue value_thaw(const LatValue *v) { |
529 | 543 | if (v->type == VAL_REF) { |
530 | | /* Thaw breaks sharing: new LatRef with deep-cloned inner */ |
531 | 0 | LatValue inner_clone = value_deep_clone(&v->as.ref.ref->value); |
532 | 0 | set_phase_recursive(&inner_clone, VTAG_FLUID); |
533 | 0 | LatValue result = value_ref(inner_clone); |
534 | 0 | value_free(&inner_clone); |
535 | 0 | result.phase = VTAG_FLUID; |
536 | 0 | return result; |
537 | 0 | } |
538 | 543 | LatValue cloned = value_deep_clone(v); |
539 | 543 | set_phase_recursive(&cloned, VTAG_FLUID); |
540 | 543 | return cloned; |
541 | 543 | } |
542 | | |
543 | | /* ── Display ── */ |
544 | | |
545 | 1.00k | void value_print(const LatValue *v, FILE *out) { |
546 | 1.00k | char *s = value_display(v); |
547 | 1.00k | fprintf(out, "%s", s); |
548 | 1.00k | free(s); |
549 | 1.00k | } |
550 | | |
551 | 4.49k | char *value_display(const LatValue *v) { |
552 | 4.49k | char *buf = NULL; |
553 | 4.49k | switch (v->type) { |
554 | 2.40k | case VAL_INT: |
555 | 2.40k | (void)asprintf(&buf, "%lld", (long long)v->as.int_val); |
556 | 2.40k | break; |
557 | 147 | case VAL_FLOAT: { |
558 | 147 | (void)asprintf(&buf, "%g", v->as.float_val); |
559 | 147 | break; |
560 | 0 | } |
561 | 471 | case VAL_BOOL: |
562 | 471 | buf = strdup(v->as.bool_val ? "true" : "false"); |
563 | 471 | break; |
564 | 1.08k | case VAL_STR: |
565 | 1.08k | buf = strdup(v->as.str_val); |
566 | 1.08k | break; |
567 | 306 | case VAL_ARRAY: { |
568 | 306 | size_t cap = 64; |
569 | 306 | buf = malloc(cap); |
570 | 306 | size_t pos = 0; |
571 | 306 | buf[pos++] = '['; |
572 | 1.14k | for (size_t i = 0; i < v->as.array.len; i++) { |
573 | 834 | if (i > 0) { buf[pos++] = ','; buf[pos++] = ' '; } |
574 | 834 | char *elem = value_display(&v->as.array.elems[i]); |
575 | 834 | size_t elen = strlen(elem); |
576 | 834 | while (pos + elen + 4 > cap) { cap *= 2; buf = realloc(buf, cap); } |
577 | 834 | memcpy(buf + pos, elem, elen); |
578 | 834 | pos += elen; |
579 | 834 | free(elem); |
580 | 834 | } |
581 | 306 | buf[pos++] = ']'; |
582 | 306 | buf[pos] = '\0'; |
583 | 306 | break; |
584 | 0 | } |
585 | 6 | case VAL_STRUCT: { |
586 | 6 | size_t cap = 64; |
587 | 6 | buf = malloc(cap); |
588 | 6 | size_t pos = 0; |
589 | 6 | size_t nlen = strlen(v->as.strct.name); |
590 | 6 | while (pos + nlen + 8 > cap) { cap *= 2; buf = realloc(buf, cap); } |
591 | 6 | memcpy(buf + pos, v->as.strct.name, nlen); |
592 | 6 | pos += nlen; |
593 | 6 | buf[pos++] = ' '; |
594 | 6 | buf[pos++] = '{'; |
595 | 6 | buf[pos++] = ' '; |
596 | 18 | for (size_t i = 0; i < v->as.strct.field_count; i++) { |
597 | 12 | if (i > 0) { buf[pos++] = ','; buf[pos++] = ' '; } |
598 | 12 | const char *fname = v->as.strct.field_names[i]; |
599 | 12 | size_t flen = strlen(fname); |
600 | 12 | char *fval = value_display(&v->as.strct.field_values[i]); |
601 | 12 | size_t vlen = strlen(fval); |
602 | 12 | while (pos + flen + vlen + 8 > cap) { cap *= 2; buf = realloc(buf, cap); } |
603 | 12 | memcpy(buf + pos, fname, flen); |
604 | 12 | pos += flen; |
605 | 12 | buf[pos++] = ':'; |
606 | 12 | buf[pos++] = ' '; |
607 | 12 | memcpy(buf + pos, fval, vlen); |
608 | 12 | pos += vlen; |
609 | 12 | free(fval); |
610 | 12 | } |
611 | 6 | buf[pos++] = ' '; |
612 | 6 | buf[pos++] = '}'; |
613 | 6 | buf[pos] = '\0'; |
614 | 6 | break; |
615 | 0 | } |
616 | 3 | case VAL_CLOSURE: { |
617 | 3 | size_t cap = 64; |
618 | 3 | buf = malloc(cap); |
619 | 3 | size_t pos = 0; |
620 | 3 | const char *prefix = "<closure|"; |
621 | 3 | size_t plen = strlen(prefix); |
622 | 3 | memcpy(buf, prefix, plen); |
623 | 3 | pos = plen; |
624 | 3 | if (v->as.closure.param_names) { |
625 | 6 | for (size_t i = 0; i < v->as.closure.param_count; i++) { |
626 | 3 | if (i > 0) { buf[pos++] = ','; buf[pos++] = ' '; } |
627 | 3 | const char *pn = v->as.closure.param_names[i]; |
628 | 3 | size_t nl = strlen(pn); |
629 | 3 | while (pos + nl + 4 > cap) { cap *= 2; buf = realloc(buf, cap); } |
630 | 3 | memcpy(buf + pos, pn, nl); |
631 | 3 | pos += nl; |
632 | 3 | } |
633 | 3 | } |
634 | 3 | buf[pos++] = '|'; |
635 | 3 | buf[pos++] = '>'; |
636 | 3 | buf[pos] = '\0'; |
637 | 3 | break; |
638 | 0 | } |
639 | 15 | case VAL_UNIT: |
640 | 15 | buf = strdup("()"); |
641 | 15 | break; |
642 | 27 | case VAL_NIL: |
643 | 27 | buf = strdup("nil"); |
644 | 27 | break; |
645 | 0 | case VAL_RANGE: |
646 | 0 | (void)asprintf(&buf, "%lld..%lld", (long long)v->as.range.start, (long long)v->as.range.end); |
647 | 0 | break; |
648 | 0 | case VAL_CHANNEL: |
649 | 0 | buf = strdup("<Channel>"); |
650 | 0 | break; |
651 | 9 | case VAL_ENUM: { |
652 | 9 | if (v->as.enm.payload_count == 0) { |
653 | 3 | (void)asprintf(&buf, "%s::%s", v->as.enm.enum_name, v->as.enm.variant_name); |
654 | 6 | } else { |
655 | 6 | size_t ecap = 64; |
656 | 6 | buf = malloc(ecap); |
657 | 6 | size_t epos = 0; |
658 | 6 | size_t enlen = strlen(v->as.enm.enum_name); |
659 | 6 | size_t vnlen = strlen(v->as.enm.variant_name); |
660 | 6 | while (epos + enlen + vnlen + 8 > ecap) { ecap *= 2; buf = realloc(buf, ecap); } |
661 | 6 | memcpy(buf + epos, v->as.enm.enum_name, enlen); epos += enlen; |
662 | 6 | buf[epos++] = ':'; buf[epos++] = ':'; |
663 | 6 | memcpy(buf + epos, v->as.enm.variant_name, vnlen); epos += vnlen; |
664 | 6 | buf[epos++] = '('; |
665 | 15 | for (size_t i = 0; i < v->as.enm.payload_count; i++) { |
666 | 9 | if (i > 0) { buf[epos++] = ','; buf[epos++] = ' '; } |
667 | 9 | char *elem = value_display(&v->as.enm.payload[i]); |
668 | 9 | size_t elen = strlen(elem); |
669 | 9 | while (epos + elen + 4 > ecap) { ecap *= 2; buf = realloc(buf, ecap); } |
670 | 9 | memcpy(buf + epos, elem, elen); epos += elen; |
671 | 9 | free(elem); |
672 | 9 | } |
673 | 6 | buf[epos++] = ')'; |
674 | 6 | buf[epos] = '\0'; |
675 | 6 | } |
676 | 9 | break; |
677 | 0 | } |
678 | 0 | case VAL_SET: { |
679 | 0 | size_t cap2 = 64; |
680 | 0 | buf = malloc(cap2); |
681 | 0 | size_t pos2 = 0; |
682 | 0 | memcpy(buf, "Set{", 4); pos2 = 4; |
683 | 0 | bool sfirst = true; |
684 | 0 | for (size_t i = 0; i < v->as.set.map->cap; i++) { |
685 | 0 | if (v->as.set.map->entries[i].state != MAP_OCCUPIED) continue; |
686 | 0 | if (!sfirst) { while (pos2 + 3 > cap2) { cap2 *= 2; buf = realloc(buf, cap2); } buf[pos2++] = ','; buf[pos2++] = ' '; } |
687 | 0 | sfirst = false; |
688 | 0 | LatValue *sv = (LatValue *)v->as.set.map->entries[i].value; |
689 | 0 | char *elem = value_display(sv); |
690 | 0 | size_t elen = strlen(elem); |
691 | 0 | while (pos2 + elen + 4 > cap2) { cap2 *= 2; buf = realloc(buf, cap2); } |
692 | 0 | memcpy(buf + pos2, elem, elen); pos2 += elen; |
693 | 0 | free(elem); |
694 | 0 | } |
695 | 0 | while (pos2 + 2 > cap2) { cap2 *= 2; buf = realloc(buf, cap2); } |
696 | 0 | buf[pos2++] = '}'; |
697 | 0 | buf[pos2] = '\0'; |
698 | 0 | break; |
699 | 0 | } |
700 | 6 | case VAL_TUPLE: { |
701 | 6 | size_t tcap = 64; |
702 | 6 | buf = malloc(tcap); |
703 | 6 | size_t tpos = 0; |
704 | 6 | buf[tpos++] = '('; |
705 | 18 | for (size_t i = 0; i < v->as.tuple.len; i++) { |
706 | 12 | if (i > 0) { |
707 | 6 | while (tpos + 3 > tcap) { tcap *= 2; buf = realloc(buf, tcap); } |
708 | 6 | buf[tpos++] = ','; buf[tpos++] = ' '; |
709 | 6 | } |
710 | 12 | char *elem = value_display(&v->as.tuple.elems[i]); |
711 | 12 | size_t elen = strlen(elem); |
712 | 12 | while (tpos + elen + 4 > tcap) { tcap *= 2; buf = realloc(buf, tcap); } |
713 | 12 | memcpy(buf + tpos, elem, elen); tpos += elen; |
714 | 12 | free(elem); |
715 | 12 | } |
716 | 6 | if (v->as.tuple.len == 1) { |
717 | 3 | while (tpos + 3 > tcap) { tcap *= 2; buf = realloc(buf, tcap); } |
718 | 3 | buf[tpos++] = ','; |
719 | 3 | } |
720 | 6 | while (tpos + 2 > tcap) { tcap *= 2; buf = realloc(buf, tcap); } |
721 | 6 | buf[tpos++] = ')'; |
722 | 6 | buf[tpos] = '\0'; |
723 | 6 | break; |
724 | 0 | } |
725 | 0 | case VAL_BUFFER: |
726 | 0 | (void)asprintf(&buf, "Buffer<%zu bytes>", v->as.buffer.len); |
727 | 0 | break; |
728 | 6 | case VAL_REF: |
729 | 6 | (void)asprintf(&buf, "Ref<%s>", value_type_name(&v->as.ref.ref->value)); |
730 | 6 | break; |
731 | 3 | case VAL_MAP: { |
732 | 3 | size_t cap2 = 64; |
733 | 3 | buf = malloc(cap2); |
734 | 3 | size_t pos2 = 0; |
735 | 3 | buf[pos2++] = '{'; |
736 | 3 | bool first = true; |
737 | 51 | for (size_t i = 0; i < v->as.map.map->cap; i++) { |
738 | 48 | if (v->as.map.map->entries[i].state != MAP_OCCUPIED) continue; |
739 | 3 | if (!first) { buf[pos2++] = ','; buf[pos2++] = ' '; } |
740 | 3 | first = false; |
741 | 3 | const char *key = v->as.map.map->entries[i].key; |
742 | 3 | LatValue *mval = (LatValue *)v->as.map.map->entries[i].value; |
743 | 3 | char *vstr = value_display(mval); |
744 | 3 | size_t klen = strlen(key); |
745 | 3 | size_t vlen = strlen(vstr); |
746 | 3 | while (pos2 + klen + vlen + 8 > cap2) { cap2 *= 2; buf = realloc(buf, cap2); } |
747 | 3 | buf[pos2++] = '"'; |
748 | 3 | memcpy(buf + pos2, key, klen); pos2 += klen; |
749 | 3 | buf[pos2++] = '"'; |
750 | 3 | buf[pos2++] = ':'; |
751 | 3 | buf[pos2++] = ' '; |
752 | 3 | memcpy(buf + pos2, vstr, vlen); pos2 += vlen; |
753 | 3 | free(vstr); |
754 | 3 | } |
755 | 3 | while (pos2 + 2 > cap2) { cap2 *= 2; buf = realloc(buf, cap2); } |
756 | 3 | buf[pos2++] = '}'; |
757 | 3 | buf[pos2] = '\0'; |
758 | 3 | break; |
759 | 0 | } |
760 | 4.49k | } |
761 | 4.49k | return buf; |
762 | 4.49k | } |
763 | | |
764 | 665 | char *value_repr(const LatValue *v) { |
765 | 665 | if (v->type == VAL_STR) { |
766 | | /* Wrap strings in double quotes */ |
767 | 3 | size_t slen = strlen(v->as.str_val); |
768 | 3 | char *buf = malloc(slen + 3); |
769 | 3 | buf[0] = '"'; |
770 | 3 | memcpy(buf + 1, v->as.str_val, slen); |
771 | 3 | buf[slen + 1] = '"'; |
772 | 3 | buf[slen + 2] = '\0'; |
773 | 3 | return buf; |
774 | 3 | } |
775 | 662 | if (v->type == VAL_BUFFER) { |
776 | | /* Buffer<N bytes: XX XX XX ...> (show first 8 bytes hex) */ |
777 | 0 | size_t show = v->as.buffer.len < 8 ? v->as.buffer.len : 8; |
778 | 0 | size_t cap = 64 + show * 3; |
779 | 0 | char *buf = malloc(cap); |
780 | 0 | int pos = snprintf(buf, cap, "Buffer<%zu bytes:", v->as.buffer.len); |
781 | 0 | for (size_t i = 0; i < show; i++) |
782 | 0 | pos += snprintf(buf + pos, cap - (size_t)pos, " %02x", v->as.buffer.data[i]); |
783 | 0 | if (v->as.buffer.len > 8) |
784 | 0 | pos += snprintf(buf + pos, cap - (size_t)pos, " ..."); |
785 | 0 | snprintf(buf + pos, cap - (size_t)pos, ">"); |
786 | 0 | return buf; |
787 | 0 | } |
788 | | /* Everything else uses standard display */ |
789 | 662 | return value_display(v); |
790 | 662 | } |
791 | | |
792 | | /* ── Type name ── */ |
793 | | |
794 | 19 | const char *value_type_name(const LatValue *v) { |
795 | 19 | switch (v->type) { |
796 | 7 | case VAL_INT: return "Int"; |
797 | 0 | case VAL_FLOAT: return "Float"; |
798 | 0 | case VAL_BOOL: return "Bool"; |
799 | 3 | case VAL_STR: return "String"; |
800 | 0 | case VAL_ARRAY: return "Array"; |
801 | 0 | case VAL_STRUCT: return "Struct"; |
802 | 0 | case VAL_CLOSURE: return "Closure"; |
803 | 0 | case VAL_UNIT: return "Unit"; |
804 | 3 | case VAL_NIL: return "Nil"; |
805 | 0 | case VAL_RANGE: return "Range"; |
806 | 6 | case VAL_MAP: return "Map"; |
807 | 0 | case VAL_CHANNEL: return "Channel"; |
808 | 0 | case VAL_ENUM: return "Enum"; |
809 | 0 | case VAL_SET: return "Set"; |
810 | 0 | case VAL_TUPLE: return "Tuple"; |
811 | 0 | case VAL_BUFFER: return "Buffer"; |
812 | 0 | case VAL_REF: return "Ref"; |
813 | 19 | } |
814 | 0 | return "?"; |
815 | 19 | } |
816 | | |
817 | | /* ── Equality ── */ |
818 | | |
819 | 9.23k | bool value_eq(const LatValue *a, const LatValue *b) { |
820 | 9.23k | if (a->type != b->type) return false; |
821 | 9.15k | switch (a->type) { |
822 | 519 | case VAL_INT: return a->as.int_val == b->as.int_val; |
823 | 0 | case VAL_FLOAT: return a->as.float_val == b->as.float_val; |
824 | 12 | case VAL_BOOL: return a->as.bool_val == b->as.bool_val; |
825 | 8.56k | case VAL_STR: return strcmp(a->as.str_val, b->as.str_val) == 0; |
826 | 0 | case VAL_UNIT: return true; |
827 | 26 | case VAL_NIL: return true; |
828 | 0 | case VAL_RANGE: return a->as.range.start == b->as.range.start && |
829 | 0 | a->as.range.end == b->as.range.end; |
830 | 0 | case VAL_ARRAY: |
831 | 0 | if (a->as.array.len != b->as.array.len) return false; |
832 | 0 | for (size_t i = 0; i < a->as.array.len; i++) { |
833 | 0 | if (!value_eq(&a->as.array.elems[i], &b->as.array.elems[i])) |
834 | 0 | return false; |
835 | 0 | } |
836 | 0 | return true; |
837 | 0 | case VAL_STRUCT: |
838 | 0 | if (strcmp(a->as.strct.name, b->as.strct.name) != 0) return false; |
839 | 0 | if (a->as.strct.field_count != b->as.strct.field_count) return false; |
840 | 0 | for (size_t i = 0; i < a->as.strct.field_count; i++) { |
841 | 0 | if (strcmp(a->as.strct.field_names[i], b->as.strct.field_names[i]) != 0) |
842 | 0 | return false; |
843 | 0 | if (!value_eq(&a->as.strct.field_values[i], &b->as.strct.field_values[i])) |
844 | 0 | return false; |
845 | 0 | } |
846 | 0 | return true; |
847 | 0 | case VAL_CLOSURE: return false; |
848 | 0 | case VAL_CHANNEL: |
849 | 0 | return a->as.channel.ch == b->as.channel.ch; |
850 | 12 | case VAL_ENUM: |
851 | 12 | if (strcmp(a->as.enm.enum_name, b->as.enm.enum_name) != 0) return false; |
852 | 12 | if (strcmp(a->as.enm.variant_name, b->as.enm.variant_name) != 0) return false; |
853 | 6 | if (a->as.enm.payload_count != b->as.enm.payload_count) return false; |
854 | 6 | for (size_t i = 0; i < a->as.enm.payload_count; i++) { |
855 | 0 | if (!value_eq(&a->as.enm.payload[i], &b->as.enm.payload[i])) return false; |
856 | 0 | } |
857 | 6 | return true; |
858 | 0 | case VAL_MAP: { |
859 | 0 | if (lat_map_len(a->as.map.map) != lat_map_len(b->as.map.map)) return false; |
860 | 0 | for (size_t i = 0; i < a->as.map.map->cap; i++) { |
861 | 0 | if (a->as.map.map->entries[i].state != MAP_OCCUPIED) continue; |
862 | 0 | const char *key = a->as.map.map->entries[i].key; |
863 | 0 | LatValue *av = (LatValue *)a->as.map.map->entries[i].value; |
864 | 0 | LatValue *bv = (LatValue *)lat_map_get(b->as.map.map, key); |
865 | 0 | if (!bv || !value_eq(av, bv)) return false; |
866 | 0 | } |
867 | 0 | return true; |
868 | 0 | } |
869 | 0 | case VAL_SET: { |
870 | 0 | if (lat_map_len(a->as.set.map) != lat_map_len(b->as.set.map)) return false; |
871 | 0 | for (size_t i = 0; i < a->as.set.map->cap; i++) { |
872 | 0 | if (a->as.set.map->entries[i].state != MAP_OCCUPIED) continue; |
873 | 0 | const char *key = a->as.set.map->entries[i].key; |
874 | 0 | if (!lat_map_contains(b->as.set.map, key)) return false; |
875 | 0 | } |
876 | 0 | return true; |
877 | 0 | } |
878 | 6 | case VAL_TUPLE: |
879 | 6 | if (a->as.tuple.len != b->as.tuple.len) return false; |
880 | 21 | for (size_t i = 0; i < a->as.tuple.len; i++) { |
881 | 18 | if (!value_eq(&a->as.tuple.elems[i], &b->as.tuple.elems[i])) |
882 | 3 | return false; |
883 | 18 | } |
884 | 3 | return true; |
885 | 6 | case VAL_BUFFER: |
886 | 6 | if (a->as.buffer.len != b->as.buffer.len) return false; |
887 | 6 | return memcmp(a->as.buffer.data, b->as.buffer.data, a->as.buffer.len) == 0; |
888 | 6 | case VAL_REF: |
889 | 6 | return a->as.ref.ref == b->as.ref.ref; |
890 | 9.15k | } |
891 | 0 | return false; |
892 | 9.15k | } |
893 | | |
894 | | /* ── Free ── */ |
895 | | |
896 | 223k | static void val_dealloc(LatValue *v, void *ptr) { |
897 | 223k | if (!ptr) return; |
898 | 219k | if (v->region_id != (size_t)-1) return; /* arena-backed: no-op */ |
899 | 219k | lat_free(ptr); |
900 | 219k | } |
901 | | |
902 | 577k | void value_free(LatValue *v) { |
903 | 577k | if (v->region_id != (size_t)-1) { |
904 | 17.1k | memset(v, 0, sizeof(*v)); /* arena owns everything */ |
905 | 17.1k | return; |
906 | 17.1k | } |
907 | 560k | switch (v->type) { |
908 | 116k | case VAL_STR: |
909 | 116k | val_dealloc(v, v->as.str_val); |
910 | 116k | break; |
911 | 9.67k | case VAL_ARRAY: |
912 | 59.0k | for (size_t i = 0; i < v->as.array.len; i++) |
913 | 49.4k | value_free(&v->as.array.elems[i]); |
914 | 9.67k | val_dealloc(v, v->as.array.elems); |
915 | 9.67k | break; |
916 | 3.93k | case VAL_STRUCT: |
917 | 3.93k | val_dealloc(v, v->as.strct.name); |
918 | 11.8k | for (size_t i = 0; i < v->as.strct.field_count; i++) { |
919 | 7.89k | val_dealloc(v, v->as.strct.field_names[i]); |
920 | 7.89k | value_free(&v->as.strct.field_values[i]); |
921 | 7.89k | } |
922 | 3.93k | val_dealloc(v, v->as.strct.field_names); |
923 | 3.93k | val_dealloc(v, v->as.strct.field_values); |
924 | 3.93k | val_dealloc(v, v->as.strct.field_phases); |
925 | 3.93k | break; |
926 | 309k | case VAL_CLOSURE: |
927 | 309k | if (v->as.closure.param_names) { |
928 | 62.7k | for (size_t i = 0; i < v->as.closure.param_count; i++) |
929 | 38.3k | val_dealloc(v, v->as.closure.param_names[i]); |
930 | 24.4k | val_dealloc(v, v->as.closure.param_names); |
931 | 24.4k | } |
932 | | /* Don't free compiled bytecode closures' env — they store ObjUpvalue**, |
933 | | * not Env*. The VM manages upvalue lifetime. */ |
934 | 309k | if (v->as.closure.captured_env && |
935 | 309k | !(v->as.closure.body == NULL && v->as.closure.native_fn != NULL)) |
936 | 9.42k | env_release(v->as.closure.captured_env); |
937 | 309k | break; |
938 | 9.82k | case VAL_MAP: |
939 | 9.82k | if (v->as.map.map) { |
940 | | /* Free each stored LatValue before freeing the map */ |
941 | 190k | for (size_t i = 0; i < v->as.map.map->cap; i++) { |
942 | 181k | if (v->as.map.map->entries[i].state == MAP_OCCUPIED) { |
943 | 43.8k | LatValue *mv = (LatValue *)v->as.map.map->entries[i].value; |
944 | 43.8k | value_free(mv); |
945 | 43.8k | } |
946 | 181k | } |
947 | 9.82k | lat_map_free(v->as.map.map); |
948 | 9.82k | val_dealloc(v, v->as.map.map); |
949 | 9.82k | } |
950 | 9.82k | if (v->as.map.key_phases) { |
951 | 21 | lat_map_free(v->as.map.key_phases); |
952 | 21 | val_dealloc(v, v->as.map.key_phases); |
953 | 21 | } |
954 | 9.82k | break; |
955 | 155 | case VAL_CHANNEL: |
956 | 155 | if (v->as.channel.ch) |
957 | 155 | channel_release(v->as.channel.ch); |
958 | 155 | break; |
959 | 94 | case VAL_ENUM: |
960 | 94 | val_dealloc(v, v->as.enm.enum_name); |
961 | 94 | val_dealloc(v, v->as.enm.variant_name); |
962 | 94 | if (v->as.enm.payload) { |
963 | 50 | for (size_t i = 0; i < v->as.enm.payload_count; i++) |
964 | 30 | value_free(&v->as.enm.payload[i]); |
965 | 20 | val_dealloc(v, v->as.enm.payload); |
966 | 20 | } |
967 | 94 | break; |
968 | 134 | case VAL_SET: |
969 | 134 | if (v->as.set.map) { |
970 | 2.27k | for (size_t i = 0; i < v->as.set.map->cap; i++) { |
971 | 2.14k | if (v->as.set.map->entries[i].state == MAP_OCCUPIED) { |
972 | 251 | LatValue *sv = (LatValue *)v->as.set.map->entries[i].value; |
973 | 251 | value_free(sv); |
974 | 251 | } |
975 | 2.14k | } |
976 | 134 | lat_map_free(v->as.set.map); |
977 | 134 | val_dealloc(v, v->as.set.map); |
978 | 134 | } |
979 | 134 | break; |
980 | 79 | case VAL_TUPLE: |
981 | 292 | for (size_t i = 0; i < v->as.tuple.len; i++) |
982 | 213 | value_free(&v->as.tuple.elems[i]); |
983 | 79 | val_dealloc(v, v->as.tuple.elems); |
984 | 79 | break; |
985 | 243 | case VAL_BUFFER: |
986 | 243 | val_dealloc(v, v->as.buffer.data); |
987 | 243 | break; |
988 | 163 | case VAL_REF: |
989 | 163 | ref_release(v->as.ref.ref); |
990 | 163 | break; |
991 | 109k | default: |
992 | 109k | break; |
993 | 560k | } |
994 | 560k | memset(v, 0, sizeof(*v)); |
995 | 560k | } |
996 | | |
997 | | /* ── Truthiness ── */ |
998 | | |
999 | 6.63k | bool value_is_truthy(const LatValue *v) { |
1000 | 6.63k | switch (v->type) { |
1001 | 6.62k | case VAL_BOOL: return v->as.bool_val; |
1002 | 0 | case VAL_INT: return v->as.int_val != 0; |
1003 | 0 | case VAL_FLOAT: return v->as.float_val != 0.0; |
1004 | 0 | case VAL_STR: return v->as.str_val[0] != '\0'; |
1005 | 0 | case VAL_UNIT: return false; |
1006 | 8 | case VAL_NIL: return false; |
1007 | 0 | case VAL_MAP: return lat_map_len(v->as.map.map) > 0; |
1008 | 0 | case VAL_SET: return lat_map_len(v->as.set.map) > 0; |
1009 | 0 | case VAL_TUPLE: return v->as.tuple.len > 0; |
1010 | 0 | case VAL_CHANNEL: return true; |
1011 | 0 | case VAL_BUFFER: return v->as.buffer.len > 0; |
1012 | 0 | case VAL_REF: return true; |
1013 | 3 | default: return true; |
1014 | 6.63k | } |
1015 | 6.63k | } |