/Users/alexjokela/projects/lattice/src/regvm.c
Line | Count | Source |
1 | | #include "regvm.h" |
2 | | #include "regopcode.h" |
3 | | #include "runtime.h" |
4 | | #include "value.h" |
5 | | #include "env.h" |
6 | | #include "stackvm.h" /* For ObjUpvalue */ |
7 | | #include "channel.h" |
8 | | #include "lexer.h" |
9 | | #include "parser.h" |
10 | | #include "ast.h" |
11 | | #include "builtins.h" |
12 | | #include "ext.h" |
13 | | #include "memory.h" |
14 | | #include <stdlib.h> |
15 | | #include <string.h> |
16 | | #include <stdio.h> |
17 | | #include <stdarg.h> |
18 | | #include <limits.h> |
19 | | #ifndef __EMSCRIPTEN__ |
20 | | #include <pthread.h> |
21 | | #endif |
22 | | |
23 | | /* Native function marker — same sentinel as stack VM (defined in vm.c) */ |
24 | 59.9k | #define VM_NATIVE_MARKER ((struct Expr **)(uintptr_t)0x1) |
25 | | /* Extension function marker — same sentinel as stack VM */ |
26 | 13.1k | #define VM_EXT_MARKER ((struct Expr **)(uintptr_t)0x2) |
27 | | |
28 | | /* ── RegChunk implementation ── */ |
29 | | |
30 | 4.27k | RegChunk *regchunk_new(void) { |
31 | 4.27k | RegChunk *c = calloc(1, sizeof(RegChunk)); |
32 | 4.27k | c->magic = REGCHUNK_MAGIC; |
33 | 4.27k | c->code_cap = 128; |
34 | 4.27k | c->code = malloc(c->code_cap * sizeof(RegInstr)); |
35 | 4.27k | c->const_cap = 32; |
36 | 4.27k | c->constants = malloc(c->const_cap * sizeof(LatValue)); |
37 | 4.27k | c->lines_cap = 128; |
38 | 4.27k | c->lines = malloc(c->lines_cap * sizeof(int)); |
39 | 4.27k | c->local_name_cap = 0; |
40 | 4.27k | c->local_names = NULL; |
41 | 4.27k | c->name = NULL; |
42 | 4.27k | return c; |
43 | 4.27k | } |
44 | | |
45 | 4.27k | void regchunk_free(RegChunk *c) { |
46 | 4.27k | if (!c) return; |
47 | | /* Free sub-chunks stored in closure constants */ |
48 | 39.0k | for (size_t i = 0; i < c->const_len; i++) { |
49 | 34.7k | LatValue *v = &c->constants[i]; |
50 | 34.7k | if (v->type == VAL_CLOSURE && v->as.closure.body == NULL && |
51 | 34.7k | v->as.closure.native_fn != NULL && |
52 | 34.7k | v->as.closure.default_values != VM_NATIVE_MARKER && |
53 | 34.7k | v->as.closure.default_values != VM_EXT_MARKER) { |
54 | 3.33k | regchunk_free((RegChunk *)v->as.closure.native_fn); |
55 | 3.33k | v->as.closure.native_fn = NULL; |
56 | 31.4k | } else { |
57 | 31.4k | value_free(v); |
58 | 31.4k | } |
59 | 34.7k | } |
60 | 4.27k | free(c->constants); |
61 | 4.27k | free(c->code); |
62 | 4.27k | free(c->lines); |
63 | 4.27k | if (c->local_names) { |
64 | 53.2k | for (size_t i = 0; i < c->local_name_cap; i++) |
65 | 50.3k | free(c->local_names[i]); |
66 | 2.92k | free(c->local_names); |
67 | 2.92k | } |
68 | 4.27k | free(c->name); |
69 | 4.27k | free(c->param_phases); |
70 | 4.27k | if (c->export_names) { |
71 | 18 | for (size_t i = 0; i < c->export_count; i++) |
72 | 12 | free(c->export_names[i]); |
73 | 6 | free(c->export_names); |
74 | 6 | } |
75 | 4.27k | free(c); |
76 | 4.27k | } |
77 | | |
78 | 112k | size_t regchunk_write(RegChunk *c, RegInstr instr, int line) { |
79 | 112k | if (c->code_len >= c->code_cap) { |
80 | 90 | c->code_cap *= 2; |
81 | 90 | c->code = realloc(c->code, c->code_cap * sizeof(RegInstr)); |
82 | 90 | } |
83 | 112k | if (c->lines_len >= c->lines_cap) { |
84 | 90 | c->lines_cap *= 2; |
85 | 90 | c->lines = realloc(c->lines, c->lines_cap * sizeof(int)); |
86 | 90 | } |
87 | 112k | size_t offset = c->code_len; |
88 | 112k | c->code[c->code_len++] = instr; |
89 | 112k | c->lines[c->lines_len++] = line; |
90 | 112k | return offset; |
91 | 112k | } |
92 | | |
93 | 34.7k | size_t regchunk_add_constant(RegChunk *c, LatValue val) { |
94 | 34.7k | if (c->const_len >= c->const_cap) { |
95 | 168 | c->const_cap *= 2; |
96 | 168 | c->constants = realloc(c->constants, c->const_cap * sizeof(LatValue)); |
97 | 168 | } |
98 | 34.7k | c->constants[c->const_len] = val; |
99 | 34.7k | return c->const_len++; |
100 | 34.7k | } |
101 | | |
102 | 8.34k | void regchunk_set_local_name(RegChunk *c, size_t reg, const char *name) { |
103 | 8.34k | if (reg >= c->local_name_cap) { |
104 | 2.96k | size_t old_cap = c->local_name_cap; |
105 | 2.96k | c->local_name_cap = reg + 16; |
106 | 2.96k | c->local_names = realloc(c->local_names, c->local_name_cap * sizeof(char *)); |
107 | 53.3k | for (size_t i = old_cap; i < c->local_name_cap; i++) |
108 | 50.3k | c->local_names[i] = NULL; |
109 | 2.96k | } |
110 | 8.34k | free(c->local_names[reg]); |
111 | 8.34k | c->local_names[reg] = name ? strdup(name) : NULL; |
112 | 8.34k | } |
113 | | |
114 | | /* ── Register VM ── */ |
115 | | |
116 | 844 | void regvm_init(RegVM *vm, LatRuntime *rt) { |
117 | 844 | memset(vm, 0, sizeof(RegVM)); |
118 | 844 | vm->fn_chunk_cap = 16; |
119 | 844 | vm->fn_chunks = malloc(vm->fn_chunk_cap * sizeof(RegChunk *)); |
120 | 844 | vm->module_cache = NULL; |
121 | 844 | vm->ephemeral = bump_arena_new(); |
122 | 844 | vm->rt = rt; |
123 | 844 | vm->env = rt->env; |
124 | 844 | vm->struct_meta = rt->struct_meta; |
125 | | /* Initialize register stack to nil */ |
126 | 13.8M | for (size_t i = 0; i < REGVM_REG_MAX * REGVM_FRAMES_MAX; i++) { |
127 | 13.8M | vm->reg_stack[i] = value_nil(); |
128 | 13.8M | } |
129 | 844 | } |
130 | | |
131 | 844 | void regvm_free(RegVM *vm) { |
132 | | /* Clear thread-local runtime pointer if it still refers to this VM's runtime, |
133 | | * preventing dangling pointer after the caller's stack-allocated LatRuntime dies. */ |
134 | 844 | if (lat_runtime_current() == vm->rt) |
135 | 844 | lat_runtime_set_current(NULL); |
136 | | |
137 | | /* Don't free env/struct_meta — runtime owns them */ |
138 | 944 | for (size_t i = 0; i < vm->fn_chunk_count; i++) |
139 | 100 | regchunk_free(vm->fn_chunks[i]); |
140 | 844 | free(vm->fn_chunks); |
141 | 844 | free(vm->error); |
142 | 844 | if (vm->module_cache) { |
143 | 1.46k | for (size_t i = 0; i < vm->module_cache->cap; i++) { |
144 | 1.37k | if (vm->module_cache->entries[i].state == MAP_OCCUPIED) { |
145 | 87 | LatValue *v = (LatValue *)vm->module_cache->entries[i].value; |
146 | 87 | value_free(v); |
147 | 87 | } |
148 | 1.37k | } |
149 | 86 | lat_map_free(vm->module_cache); |
150 | 86 | free(vm->module_cache); |
151 | 86 | vm->module_cache = NULL; |
152 | 86 | } |
153 | 844 | if (vm->ephemeral) { |
154 | 844 | bump_arena_free(vm->ephemeral); |
155 | 844 | vm->ephemeral = NULL; |
156 | 844 | } |
157 | | /* Free register values */ |
158 | 51.0k | for (size_t i = 0; i < vm->reg_stack_top; i++) |
159 | 50.1k | value_free_inline(&vm->reg_stack[i]); |
160 | | /* Free open upvalues */ |
161 | 844 | ObjUpvalue *uv = vm->open_upvalues; |
162 | 844 | while (uv) { |
163 | 0 | ObjUpvalue *next = uv->next; |
164 | 0 | value_free(&uv->closed); |
165 | 0 | free(uv); |
166 | 0 | uv = next; |
167 | 0 | } |
168 | | /* Reactions, bonds, seeds are owned by LatRuntime — not freed here */ |
169 | 844 | } |
170 | | |
171 | 100 | void regvm_track_chunk(RegVM *vm, RegChunk *ch) { |
172 | 100 | if (vm->fn_chunk_count >= vm->fn_chunk_cap) { |
173 | 0 | vm->fn_chunk_cap *= 2; |
174 | 0 | vm->fn_chunks = realloc(vm->fn_chunks, vm->fn_chunk_cap * sizeof(RegChunk *)); |
175 | 0 | } |
176 | 100 | vm->fn_chunks[vm->fn_chunk_count++] = ch; |
177 | 100 | } |
178 | | |
179 | | /* ── Value cloning (same fast-path as stack VM) ── */ |
180 | | |
181 | 40.0k | static inline LatValue rvm_clone(const LatValue *src) { |
182 | 40.0k | switch (src->type) { |
183 | 10.7k | case VAL_INT: case VAL_FLOAT: case VAL_BOOL: |
184 | 14.7k | case VAL_UNIT: case VAL_NIL: case VAL_RANGE: { |
185 | 14.7k | LatValue v = *src; |
186 | 14.7k | v.region_id = REGION_NONE; |
187 | 14.7k | return v; |
188 | 14.7k | } |
189 | 13.0k | case VAL_STR: { |
190 | 13.0k | LatValue v = *src; |
191 | 13.0k | v.as.str_val = strdup(src->as.str_val); |
192 | 13.0k | v.region_id = REGION_NONE; |
193 | 13.0k | return v; |
194 | 14.7k | } |
195 | 7.81k | case VAL_CLOSURE: { |
196 | 7.81k | if (src->as.closure.body == NULL && src->as.closure.native_fn != NULL && |
197 | 7.81k | src->as.closure.default_values != VM_NATIVE_MARKER && |
198 | 7.81k | src->as.closure.default_values != VM_EXT_MARKER) { |
199 | 6.15k | LatValue v = *src; |
200 | 6.15k | if (src->as.closure.param_names) { |
201 | 4.25k | v.as.closure.param_names = malloc(src->as.closure.param_count * sizeof(char *)); |
202 | 11.2k | for (size_t i = 0; i < src->as.closure.param_count; i++) |
203 | 6.94k | v.as.closure.param_names[i] = strdup(src->as.closure.param_names[i]); |
204 | 4.25k | } |
205 | 6.15k | return v; |
206 | 6.15k | } |
207 | 1.66k | return value_deep_clone(src); |
208 | 7.81k | } |
209 | 1.09k | case VAL_ARRAY: { |
210 | 1.09k | LatValue v = *src; |
211 | 1.09k | size_t len = src->as.array.len; |
212 | 1.09k | size_t cap = src->as.array.cap > 0 ? src->as.array.cap : (len > 0 ? len : 1); |
213 | 1.09k | v.as.array.elems = malloc(cap * sizeof(LatValue)); |
214 | 1.09k | v.as.array.cap = cap; |
215 | 9.29k | for (size_t i = 0; i < len; i++) |
216 | 8.20k | v.as.array.elems[i] = rvm_clone(&src->as.array.elems[i]); |
217 | 1.09k | v.region_id = REGION_NONE; |
218 | 1.09k | return v; |
219 | 7.81k | } |
220 | 3.38k | default: |
221 | 3.38k | return value_deep_clone(src); |
222 | 40.0k | } |
223 | 40.0k | } |
224 | | |
225 | | /* ── Runtime error ── */ |
226 | | |
227 | | /* Basic error (no exception handler check, for use outside dispatch loop) */ |
228 | 0 | static RegVMResult rvm_error(RegVM *vm, const char *fmt, ...) { |
229 | 0 | char *msg = NULL; |
230 | 0 | va_list args; |
231 | 0 | va_start(args, fmt); |
232 | 0 | (void)vasprintf(&msg, fmt, args); |
233 | 0 | va_end(args); |
234 | |
|
235 | 0 | RegCallFrame *f = &vm->frames[vm->frame_count - 1]; |
236 | 0 | int line = 0; |
237 | 0 | if (f->ip > f->chunk->code) { |
238 | 0 | size_t offset = (size_t)(f->ip - f->chunk->code - 1); |
239 | 0 | if (offset < f->chunk->lines_len) |
240 | 0 | line = f->chunk->lines[offset]; |
241 | 0 | } |
242 | 0 | if (line > 0) { |
243 | 0 | char *full = NULL; |
244 | 0 | (void)asprintf(&full, "[line %d] %s", line, msg); |
245 | 0 | free(msg); |
246 | 0 | vm->error = full; |
247 | 0 | } else { |
248 | 0 | vm->error = msg; |
249 | 0 | } |
250 | 0 | return REGVM_RUNTIME_ERROR; |
251 | 0 | } |
252 | | |
253 | | /* Forward declaration needed by rvm_handle_error */ |
254 | | static inline void reg_set(LatValue *r, LatValue val); |
255 | | |
256 | | /* Error handler that routes through exception handlers when available. |
257 | | * Used inside the dispatch loop via RVM_ERROR macro. |
258 | | * Returns REGVM_OK if handled (execution continues), error otherwise. */ |
259 | | static RegVMResult rvm_handle_error(RegVM *vm, RegCallFrame **frame_ptr, |
260 | 99 | LatValue **R_ptr, const char *fmt, ...) { |
261 | 99 | char *inner = NULL; |
262 | 99 | va_list args; |
263 | 99 | va_start(args, fmt); |
264 | 99 | (void)vasprintf(&inner, fmt, args); |
265 | 99 | va_end(args); |
266 | | |
267 | | /* If there's an active handler, pass raw message to catch variable */ |
268 | 99 | if (vm->handler_count > 0) { |
269 | 24 | RegHandler h = vm->handlers[--vm->handler_count]; |
270 | | |
271 | | /* Unwind frames */ |
272 | 32 | while (vm->frame_count - 1 > (int)h.frame_index) { |
273 | 8 | RegCallFrame *uf = &vm->frames[vm->frame_count - 1]; |
274 | 2.05k | for (int i = 0; i < REGVM_REG_MAX; i++) |
275 | 2.04k | value_free_inline(&uf->regs[i]); |
276 | 8 | vm->frame_count--; |
277 | 8 | vm->reg_stack_top -= REGVM_REG_MAX; |
278 | 8 | } |
279 | | |
280 | 24 | *frame_ptr = &vm->frames[vm->frame_count - 1]; |
281 | 24 | *R_ptr = (*frame_ptr)->regs; |
282 | 24 | (*frame_ptr)->ip = h.ip; |
283 | 24 | reg_set(&(*R_ptr)[h.error_reg], value_string_owned(inner)); |
284 | 24 | return REGVM_OK; |
285 | 24 | } |
286 | | |
287 | | /* Uncaught error: prepend line info */ |
288 | 75 | RegCallFrame *f = &vm->frames[vm->frame_count - 1]; |
289 | 75 | int line = 0; |
290 | 75 | if (f->ip > f->chunk->code) { |
291 | 75 | size_t offset = (size_t)(f->ip - f->chunk->code - 1); |
292 | 75 | if (offset < f->chunk->lines_len) |
293 | 75 | line = f->chunk->lines[offset]; |
294 | 75 | } |
295 | 75 | if (line > 0) { |
296 | 73 | char *msg; |
297 | 73 | (void)asprintf(&msg, "[line %d] %s", line, inner); |
298 | 73 | free(inner); |
299 | 73 | vm->error = msg; |
300 | 73 | } else { |
301 | 2 | vm->error = inner; |
302 | 2 | } |
303 | 75 | return REGVM_RUNTIME_ERROR; |
304 | 99 | } |
305 | | |
306 | | /* ── Set a register (free old value, then assign) ── */ |
307 | | |
308 | 33.6k | static inline void reg_set(LatValue *r, LatValue val) { |
309 | 33.6k | value_free_inline(r); |
310 | 33.6k | *r = val; |
311 | 33.6k | } |
312 | | |
313 | | /* Forward declarations for recursive closure calls */ |
314 | | static RegVMResult regvm_dispatch(RegVM *vm, int base_frame, LatValue *result); |
315 | | static LatValue regvm_call_closure(RegVM *vm, LatValue *closure, LatValue *args, int argc); |
316 | | |
317 | | /* Run a sub-chunk within the current VM (pushes a new frame, doesn't reset state) */ |
318 | 102 | static RegVMResult regvm_run_sub(RegVM *vm, RegChunk *chunk, LatValue *result) { |
319 | 102 | if (vm->frame_count >= REGVM_FRAMES_MAX) |
320 | 0 | return rvm_error(vm, "call stack overflow"); |
321 | 102 | size_t new_base = vm->reg_stack_top; |
322 | 102 | if (new_base + REGVM_REG_MAX > REGVM_REG_MAX * REGVM_FRAMES_MAX) |
323 | 0 | return rvm_error(vm, "register stack overflow"); |
324 | 102 | LatValue *new_regs = &vm->reg_stack[new_base]; |
325 | 102 | vm->reg_stack_top += REGVM_REG_MAX; |
326 | 26.2k | for (int i = 0; i < REGVM_REG_MAX; i++) |
327 | 26.1k | new_regs[i] = value_nil(); |
328 | | |
329 | 102 | int saved_base = vm->frame_count; |
330 | 102 | RegCallFrame *new_frame = &vm->frames[vm->frame_count++]; |
331 | 102 | new_frame->chunk = chunk; |
332 | 102 | new_frame->ip = chunk->code; |
333 | 102 | new_frame->regs = new_regs; |
334 | 102 | new_frame->reg_count = REGVM_REG_MAX; |
335 | 102 | new_frame->upvalues = NULL; |
336 | 102 | new_frame->upvalue_count = 0; |
337 | 102 | new_frame->caller_result_reg = 0; |
338 | | |
339 | 102 | RegVMResult res = regvm_dispatch(vm, saved_base, result); |
340 | | |
341 | | /* Clean up any frames left by HALT (which doesn't pop the frame) */ |
342 | 104 | while (vm->frame_count > saved_base) { |
343 | 2 | RegCallFrame *f = &vm->frames[vm->frame_count - 1]; |
344 | 514 | for (int i = 0; i < REGVM_REG_MAX; i++) |
345 | 512 | value_free_inline(&f->regs[i]); |
346 | 2 | vm->frame_count--; |
347 | 2 | vm->reg_stack_top -= REGVM_REG_MAX; |
348 | 2 | } |
349 | | |
350 | 102 | return res; |
351 | 102 | } |
352 | | |
353 | | /* ── Invoke builtin method ── */ |
354 | | /* Returns true if handled, false if not a builtin */ |
355 | | |
356 | | static bool rvm_invoke_builtin(RegVM *vm, LatValue *obj, const char *method, |
357 | 2.81k | LatValue *args, int arg_count, LatValue *result) { |
358 | 2.81k | (void)vm; |
359 | 2.81k | if (obj->type == VAL_ARRAY) { |
360 | 244 | if (strcmp(method, "len") == 0 && arg_count == 0) { |
361 | 21 | *result = value_int((int64_t)obj->as.array.len); |
362 | 21 | return true; |
363 | 21 | } |
364 | 223 | if (strcmp(method, "push") == 0 && arg_count == 1) { |
365 | 131 | if (value_is_crystal(obj)) { |
366 | 0 | vm->error = strdup("cannot push to a crystal array"); |
367 | 0 | *result = value_unit(); |
368 | 0 | return true; |
369 | 0 | } |
370 | 131 | if (obj->phase == VTAG_SUBLIMATED) { |
371 | 1 | vm->error = strdup("cannot push to a sublimated array"); |
372 | 1 | *result = value_unit(); |
373 | 1 | return true; |
374 | 1 | } |
375 | | /* Check pressure constraints */ |
376 | 130 | { |
377 | 130 | if (vm->rt && vm->rt->pressure_count > 0) { |
378 | | /* Find variable name for this object by checking register names */ |
379 | 2 | RegCallFrame *cf = &vm->frames[vm->frame_count - 1]; |
380 | 2 | if (cf->chunk && cf->chunk->local_names) { |
381 | 4 | for (size_t r = 0; r < cf->chunk->local_name_cap; r++) { |
382 | 4 | if (&cf->regs[r] == obj && cf->chunk->local_names[r] && cf->chunk->local_names[r][0]) { |
383 | 2 | for (size_t pi = 0; pi < vm->rt->pressure_count; pi++) { |
384 | 2 | if (strcmp(vm->rt->pressures[pi].name, cf->chunk->local_names[r]) == 0) { |
385 | 2 | const char *mode = vm->rt->pressures[pi].mode; |
386 | 2 | if (strcmp(mode, "no_grow") == 0 || strcmp(mode, "no_resize") == 0) { |
387 | 2 | (void)asprintf(&vm->error, "pressurized (%s): cannot push to '%s'", |
388 | 2 | mode, cf->chunk->local_names[r]); |
389 | 2 | *result = value_unit(); |
390 | 2 | return true; |
391 | 2 | } |
392 | 2 | } |
393 | 2 | } |
394 | 0 | break; |
395 | 2 | } |
396 | 4 | } |
397 | 2 | } |
398 | 2 | } |
399 | 130 | } |
400 | 128 | LatValue val = rvm_clone(&args[0]); |
401 | 128 | if (obj->as.array.len >= obj->as.array.cap) { |
402 | 7 | obj->as.array.cap = obj->as.array.cap ? obj->as.array.cap * 2 : 4; |
403 | 7 | obj->as.array.elems = realloc(obj->as.array.elems, |
404 | 7 | obj->as.array.cap * sizeof(LatValue)); |
405 | 7 | } |
406 | 128 | obj->as.array.elems[obj->as.array.len++] = val; |
407 | 128 | *result = value_unit(); |
408 | 128 | return true; |
409 | 130 | } |
410 | 92 | if (strcmp(method, "pop") == 0 && arg_count == 0) { |
411 | 3 | if (value_is_crystal(obj)) { |
412 | 0 | vm->error = strdup("cannot pop from a crystal array"); |
413 | 0 | *result = value_unit(); |
414 | 0 | return true; |
415 | 0 | } |
416 | 3 | if (obj->phase == VTAG_SUBLIMATED) { |
417 | 1 | vm->error = strdup("cannot pop from a sublimated array"); |
418 | 1 | *result = value_unit(); |
419 | 1 | return true; |
420 | 1 | } |
421 | | /* Check pressure constraints */ |
422 | 2 | { |
423 | 2 | if (vm->rt && vm->rt->pressure_count > 0) { |
424 | 1 | RegCallFrame *cf = &vm->frames[vm->frame_count - 1]; |
425 | 1 | if (cf->chunk && cf->chunk->local_names) { |
426 | 2 | for (size_t r = 0; r < cf->chunk->local_name_cap; r++) { |
427 | 2 | if (&cf->regs[r] == obj && cf->chunk->local_names[r] && cf->chunk->local_names[r][0]) { |
428 | 1 | for (size_t pi = 0; pi < vm->rt->pressure_count; pi++) { |
429 | 1 | if (strcmp(vm->rt->pressures[pi].name, cf->chunk->local_names[r]) == 0) { |
430 | 1 | const char *mode = vm->rt->pressures[pi].mode; |
431 | 1 | if (strcmp(mode, "no_shrink") == 0 || strcmp(mode, "no_resize") == 0) { |
432 | 1 | (void)asprintf(&vm->error, "pressurized (%s): cannot pop from '%s'", |
433 | 1 | mode, cf->chunk->local_names[r]); |
434 | 1 | *result = value_unit(); |
435 | 1 | return true; |
436 | 1 | } |
437 | 1 | } |
438 | 1 | } |
439 | 0 | break; |
440 | 1 | } |
441 | 2 | } |
442 | 1 | } |
443 | 1 | } |
444 | 2 | } |
445 | 1 | if (obj->as.array.len == 0) { |
446 | 0 | *result = value_nil(); |
447 | 1 | } else { |
448 | 1 | *result = obj->as.array.elems[--obj->as.array.len]; |
449 | 1 | } |
450 | 1 | return true; |
451 | 2 | } |
452 | 89 | if (strcmp(method, "contains") == 0 && arg_count == 1) { |
453 | 8 | bool found = false; |
454 | 2.61k | for (size_t i = 0; i < obj->as.array.len; i++) { |
455 | 2.61k | if (value_eq(&obj->as.array.elems[i], &args[0])) { |
456 | 6 | found = true; |
457 | 6 | break; |
458 | 6 | } |
459 | 2.61k | } |
460 | 8 | *result = value_bool(found); |
461 | 8 | return true; |
462 | 8 | } |
463 | 81 | if (strcmp(method, "reverse") == 0 && arg_count == 0) { |
464 | 1 | LatValue *elems = malloc(obj->as.array.len * sizeof(LatValue)); |
465 | 4 | for (size_t i = 0; i < obj->as.array.len; i++) |
466 | 3 | elems[i] = rvm_clone(&obj->as.array.elems[obj->as.array.len - 1 - i]); |
467 | 1 | *result = value_array(elems, obj->as.array.len); |
468 | 1 | free(elems); |
469 | 1 | return true; |
470 | 1 | } |
471 | 80 | if (strcmp(method, "map") == 0 && arg_count == 1) { |
472 | 7 | LatValue *closure = &args[0]; |
473 | 7 | size_t len = obj->as.array.len; |
474 | 7 | LatValue *elems = malloc(len * sizeof(LatValue)); |
475 | 139 | for (size_t i = 0; i < len; i++) { |
476 | 132 | LatValue arg = rvm_clone(&obj->as.array.elems[i]); |
477 | 132 | elems[i] = regvm_call_closure(vm, closure, &arg, 1); |
478 | 132 | value_free(&arg); |
479 | 132 | } |
480 | 7 | *result = value_array(elems, len); |
481 | 7 | free(elems); |
482 | 7 | return true; |
483 | 7 | } |
484 | 73 | if (strcmp(method, "filter") == 0 && arg_count == 1) { |
485 | 3 | LatValue *closure = &args[0]; |
486 | 3 | size_t len = obj->as.array.len; |
487 | 3 | size_t cap = len > 0 ? len : 1; |
488 | 3 | LatValue *elems = malloc(cap * sizeof(LatValue)); |
489 | 3 | size_t out_len = 0; |
490 | 75 | for (size_t i = 0; i < len; i++) { |
491 | 72 | LatValue arg = rvm_clone(&obj->as.array.elems[i]); |
492 | 72 | LatValue pred = regvm_call_closure(vm, closure, &arg, 1); |
493 | 72 | bool keep = (pred.type == VAL_BOOL && pred.as.bool_val); |
494 | 72 | value_free(&pred); |
495 | 72 | if (keep) { |
496 | 33 | elems[out_len++] = arg; |
497 | 39 | } else { |
498 | 39 | value_free(&arg); |
499 | 39 | } |
500 | 72 | } |
501 | 3 | *result = value_array(elems, out_len); |
502 | 3 | free(elems); |
503 | 3 | return true; |
504 | 3 | } |
505 | 70 | if (strcmp(method, "join") == 0 && arg_count == 1) { |
506 | 1 | const char *sep = (args[0].type == VAL_STR) ? args[0].as.str_val : ""; |
507 | 1 | size_t sep_len = strlen(sep); |
508 | 1 | size_t n = obj->as.array.len; |
509 | 1 | char **parts = malloc(n * sizeof(char *)); |
510 | 1 | size_t *lens = malloc(n * sizeof(size_t)); |
511 | 1 | size_t total = 0; |
512 | 5 | for (size_t i = 0; i < n; i++) { |
513 | 4 | parts[i] = value_display(&obj->as.array.elems[i]); |
514 | 4 | lens[i] = strlen(parts[i]); |
515 | 4 | total += lens[i]; |
516 | 4 | } |
517 | 1 | if (n > 1) total += sep_len * (n - 1); |
518 | 1 | char *buf = malloc(total + 1); |
519 | 1 | size_t pos = 0; |
520 | 5 | for (size_t i = 0; i < n; i++) { |
521 | 4 | if (i > 0) { memcpy(buf + pos, sep, sep_len); pos += sep_len; } |
522 | 4 | memcpy(buf + pos, parts[i], lens[i]); pos += lens[i]; |
523 | 4 | free(parts[i]); |
524 | 4 | } |
525 | 1 | buf[pos] = '\0'; |
526 | 1 | free(parts); free(lens); |
527 | 1 | *result = value_string_owned(buf); |
528 | 1 | return true; |
529 | 1 | } |
530 | 70 | } |
531 | | |
532 | 2.63k | if (obj->type == VAL_STR) { |
533 | 199 | if (strcmp(method, "len") == 0 && arg_count == 0) { |
534 | 8 | *result = value_int((int64_t)strlen(obj->as.str_val)); |
535 | 8 | return true; |
536 | 8 | } |
537 | 191 | if (strcmp(method, "contains") == 0 && arg_count == 1) { |
538 | 5 | if (args[0].type == VAL_STR) { |
539 | 5 | *result = value_bool(strstr(obj->as.str_val, args[0].as.str_val) != NULL); |
540 | 5 | } else { |
541 | 0 | *result = value_bool(false); |
542 | 0 | } |
543 | 5 | return true; |
544 | 5 | } |
545 | 191 | } |
546 | | |
547 | 2.62k | if (obj->type == VAL_MAP) { |
548 | 2.26k | if (strcmp(method, "len") == 0 && arg_count == 0) { |
549 | 6 | size_t count = 0; |
550 | 102 | for (size_t i = 0; i < obj->as.map.map->cap; i++) { |
551 | 96 | if (obj->as.map.map->entries[i].state == MAP_OCCUPIED) |
552 | 9 | count++; |
553 | 96 | } |
554 | 6 | *result = value_int((int64_t)count); |
555 | 6 | return true; |
556 | 6 | } |
557 | 2.25k | if (strcmp(method, "keys") == 0 && arg_count == 0) { |
558 | 15 | size_t cap = obj->as.map.map->cap; |
559 | 15 | LatValue *keys = malloc(cap * sizeof(LatValue)); |
560 | 15 | size_t count = 0; |
561 | 255 | for (size_t i = 0; i < cap; i++) { |
562 | 240 | if (obj->as.map.map->entries[i].state == MAP_OCCUPIED) |
563 | 25 | keys[count++] = value_string(obj->as.map.map->entries[i].key); |
564 | 240 | } |
565 | 15 | *result = value_array(keys, count); |
566 | 15 | free(keys); |
567 | 15 | return true; |
568 | 15 | } |
569 | 2.24k | if (strcmp(method, "values") == 0 && arg_count == 0) { |
570 | 1 | size_t cap = obj->as.map.map->cap; |
571 | 1 | LatValue *vals = malloc(cap * sizeof(LatValue)); |
572 | 1 | size_t count = 0; |
573 | 17 | for (size_t i = 0; i < cap; i++) { |
574 | 16 | if (obj->as.map.map->entries[i].state == MAP_OCCUPIED) |
575 | 1 | vals[count++] = rvm_clone((LatValue *)obj->as.map.map->entries[i].value); |
576 | 16 | } |
577 | 1 | *result = value_array(vals, count); |
578 | 1 | free(vals); |
579 | 1 | return true; |
580 | 1 | } |
581 | 2.24k | if (strcmp(method, "get") == 0 && arg_count == 1) { |
582 | 1.00k | if (args[0].type == VAL_STR) { |
583 | 1.00k | LatValue *val = lat_map_get(obj->as.map.map, args[0].as.str_val); |
584 | 1.00k | *result = val ? rvm_clone(val) : value_nil(); |
585 | 1.00k | } else { |
586 | 0 | *result = value_nil(); |
587 | 0 | } |
588 | 1.00k | return true; |
589 | 1.00k | } |
590 | 1.24k | if (strcmp(method, "set") == 0 && arg_count == 2) { |
591 | 955 | if (args[0].type == VAL_STR) { |
592 | 955 | LatValue cloned = rvm_clone(&args[1]); |
593 | 955 | lat_map_set(obj->as.map.map, args[0].as.str_val, &cloned); |
594 | 955 | } |
595 | 955 | *result = value_unit(); |
596 | 955 | return true; |
597 | 955 | } |
598 | 287 | if ((strcmp(method, "has") == 0 || strcmp(method, "contains") == 0) && arg_count == 1) { |
599 | 111 | if (args[0].type == VAL_STR) |
600 | 111 | *result = value_bool(lat_map_get(obj->as.map.map, args[0].as.str_val) != NULL); |
601 | 0 | else |
602 | 0 | *result = value_bool(false); |
603 | 111 | return true; |
604 | 111 | } |
605 | 176 | if (strcmp(method, "entries") == 0 && arg_count == 0) { |
606 | 1 | size_t cap = obj->as.map.map->cap; |
607 | 1 | LatValue *entries = malloc(cap * sizeof(LatValue)); |
608 | 1 | size_t count = 0; |
609 | 17 | for (size_t i = 0; i < cap; i++) { |
610 | 16 | if (obj->as.map.map->entries[i].state != MAP_OCCUPIED) continue; |
611 | 1 | LatValue pair[2]; |
612 | 1 | pair[0] = value_string(obj->as.map.map->entries[i].key); |
613 | 1 | pair[1] = rvm_clone((LatValue *)obj->as.map.map->entries[i].value); |
614 | 1 | entries[count++] = value_array(pair, 2); |
615 | 1 | } |
616 | 1 | *result = value_array(entries, count); |
617 | 1 | free(entries); |
618 | 1 | return true; |
619 | 1 | } |
620 | 175 | if (strcmp(method, "merge") == 0 && arg_count == 1) { |
621 | 1 | if (args[0].type == VAL_MAP) { |
622 | | /* Mutate obj in place (like stack VM) */ |
623 | 17 | for (size_t i = 0; i < args[0].as.map.map->cap; i++) { |
624 | 16 | if (args[0].as.map.map->entries[i].state != MAP_OCCUPIED) continue; |
625 | 1 | LatValue v = rvm_clone((LatValue *)args[0].as.map.map->entries[i].value); |
626 | 1 | lat_map_set(obj->as.map.map, args[0].as.map.map->entries[i].key, &v); |
627 | 1 | } |
628 | 1 | } |
629 | 1 | *result = value_unit(); |
630 | 1 | return true; |
631 | 1 | } |
632 | 174 | if (strcmp(method, "for_each") == 0 && arg_count == 1) { |
633 | 1 | LatValue *closure = &args[0]; |
634 | 17 | for (size_t i = 0; i < obj->as.map.map->cap; i++) { |
635 | 16 | if (obj->as.map.map->entries[i].state != MAP_OCCUPIED) continue; |
636 | 1 | LatValue cb_args[2]; |
637 | 1 | cb_args[0] = value_string(obj->as.map.map->entries[i].key); |
638 | 1 | cb_args[1] = rvm_clone((LatValue *)obj->as.map.map->entries[i].value); |
639 | 1 | LatValue ret = regvm_call_closure(vm, closure, cb_args, 2); |
640 | 1 | value_free(&cb_args[0]); |
641 | 1 | value_free(&cb_args[1]); |
642 | 1 | value_free(&ret); |
643 | 1 | } |
644 | 1 | *result = value_unit(); |
645 | 1 | return true; |
646 | 1 | } |
647 | 173 | if (strcmp(method, "filter") == 0 && arg_count == 1) { |
648 | 2 | LatValue *closure = &args[0]; |
649 | 2 | LatValue filtered = value_map_new(); |
650 | 34 | for (size_t i = 0; i < obj->as.map.map->cap; i++) { |
651 | 32 | if (obj->as.map.map->entries[i].state != MAP_OCCUPIED) continue; |
652 | 4 | LatValue cb_args[2]; |
653 | 4 | cb_args[0] = value_string(obj->as.map.map->entries[i].key); |
654 | 4 | cb_args[1] = rvm_clone((LatValue *)obj->as.map.map->entries[i].value); |
655 | 4 | LatValue pred = regvm_call_closure(vm, closure, cb_args, 2); |
656 | 4 | if (pred.type == VAL_BOOL && pred.as.bool_val) { |
657 | 2 | LatValue v = rvm_clone((LatValue *)obj->as.map.map->entries[i].value); |
658 | 2 | lat_map_set(filtered.as.map.map, obj->as.map.map->entries[i].key, &v); |
659 | 2 | } |
660 | 4 | value_free(&cb_args[0]); |
661 | 4 | value_free(&cb_args[1]); |
662 | 4 | value_free(&pred); |
663 | 4 | } |
664 | 2 | *result = filtered; |
665 | 2 | return true; |
666 | 2 | } |
667 | 173 | } |
668 | | |
669 | | /* ── Array additional methods ── */ |
670 | 532 | if (obj->type == VAL_ARRAY) { |
671 | 69 | if (strcmp(method, "enumerate") == 0 && arg_count == 0) { |
672 | 1 | size_t len = obj->as.array.len; |
673 | 1 | LatValue *elems = malloc(len * sizeof(LatValue)); |
674 | 4 | for (size_t i = 0; i < len; i++) { |
675 | 3 | LatValue pair[2]; |
676 | 3 | pair[0] = value_int((int64_t)i); |
677 | 3 | pair[1] = rvm_clone(&obj->as.array.elems[i]); |
678 | 3 | elems[i] = value_array(pair, 2); |
679 | 3 | } |
680 | 1 | *result = value_array(elems, len); |
681 | 1 | free(elems); |
682 | 1 | return true; |
683 | 1 | } |
684 | 68 | if (strcmp(method, "reduce") == 0 && (arg_count == 1 || arg_count == 2)) { |
685 | 4 | LatValue *closure = &args[0]; |
686 | 4 | LatValue acc; |
687 | 4 | size_t start = 0; |
688 | 4 | if (arg_count == 2) { |
689 | 4 | acc = rvm_clone(&args[1]); |
690 | 4 | } else if (obj->as.array.len > 0) { |
691 | 0 | acc = rvm_clone(&obj->as.array.elems[0]); |
692 | 0 | start = 1; |
693 | 0 | } else { |
694 | 0 | *result = value_nil(); |
695 | 0 | return true; |
696 | 0 | } |
697 | 14 | for (size_t i = start; i < obj->as.array.len; i++) { |
698 | 10 | LatValue cb_args[2]; |
699 | 10 | cb_args[0] = acc; |
700 | 10 | cb_args[1] = rvm_clone(&obj->as.array.elems[i]); |
701 | 10 | acc = regvm_call_closure(vm, closure, cb_args, 2); |
702 | 10 | value_free(&cb_args[0]); |
703 | 10 | value_free(&cb_args[1]); |
704 | 10 | } |
705 | 4 | *result = acc; |
706 | 4 | return true; |
707 | 4 | } |
708 | 64 | if ((strcmp(method, "each") == 0 || strcmp(method, "for_each") == 0) && arg_count == 1) { |
709 | 1 | LatValue *closure = &args[0]; |
710 | 4 | for (size_t i = 0; i < obj->as.array.len; i++) { |
711 | 3 | LatValue arg = rvm_clone(&obj->as.array.elems[i]); |
712 | 3 | LatValue ret = regvm_call_closure(vm, closure, &arg, 1); |
713 | 3 | value_free(&arg); |
714 | 3 | value_free(&ret); |
715 | 3 | } |
716 | 1 | *result = value_unit(); |
717 | 1 | return true; |
718 | 1 | } |
719 | 63 | if (strcmp(method, "sort") == 0 && arg_count <= 1) { |
720 | 5 | size_t len = obj->as.array.len; |
721 | 5 | LatValue *sorted = malloc(len * sizeof(LatValue)); |
722 | 16 | for (size_t i = 0; i < len; i++) |
723 | 11 | sorted[i] = rvm_clone(&obj->as.array.elems[i]); |
724 | | /* Insertion sort */ |
725 | 11 | for (size_t i = 1; i < len; i++) { |
726 | 7 | LatValue key = sorted[i]; |
727 | 7 | int64_t j = (int64_t)i - 1; |
728 | 13 | while (j >= 0) { |
729 | 10 | bool swap = false; |
730 | 10 | if (arg_count == 1) { |
731 | 0 | LatValue cb_args[2] = { rvm_clone(&sorted[j]), rvm_clone(&key) }; |
732 | 0 | LatValue cmp = regvm_call_closure(vm, &args[0], cb_args, 2); |
733 | 0 | swap = (cmp.type == VAL_INT && cmp.as.int_val > 0) || |
734 | 0 | (cmp.type == VAL_FLOAT && cmp.as.float_val > 0); |
735 | 0 | value_free(&cmp); |
736 | 0 | value_free(&cb_args[0]); |
737 | 0 | value_free(&cb_args[1]); |
738 | 10 | } else { |
739 | 10 | if (sorted[j].type == VAL_INT && key.type == VAL_INT) |
740 | 3 | swap = sorted[j].as.int_val > key.as.int_val; |
741 | 7 | else if ((sorted[j].type == VAL_FLOAT || sorted[j].type == VAL_INT) && |
742 | 7 | (key.type == VAL_FLOAT || key.type == VAL_INT)) { |
743 | 3 | double a = sorted[j].type == VAL_FLOAT ? sorted[j].as.float_val : (double)sorted[j].as.int_val; |
744 | 3 | double b = key.type == VAL_FLOAT ? key.as.float_val : (double)key.as.int_val; |
745 | 3 | swap = a > b; |
746 | 4 | } else if (sorted[j].type == VAL_STR && key.type == VAL_STR) { |
747 | 3 | swap = strcmp(sorted[j].as.str_val, key.as.str_val) > 0; |
748 | 3 | } else { |
749 | 3 | for (size_t k = 0; k < len; k++) value_free(&sorted[k]); |
750 | 1 | free(sorted); |
751 | 1 | vm->error = strdup("sort: cannot compare values of different types"); |
752 | 1 | *result = value_unit(); |
753 | 1 | return true; |
754 | 1 | } |
755 | 10 | } |
756 | 9 | if (!swap) break; |
757 | 6 | sorted[j + 1] = sorted[j]; |
758 | 6 | j--; |
759 | 6 | } |
760 | 6 | sorted[j + 1] = key; |
761 | 6 | } |
762 | 4 | *result = value_array(sorted, len); |
763 | 4 | free(sorted); |
764 | 4 | return true; |
765 | 5 | } |
766 | 58 | if (strcmp(method, "sort_by") == 0 && arg_count == 1) { |
767 | 2 | LatValue *closure = &args[0]; |
768 | 2 | size_t len = obj->as.array.len; |
769 | 2 | LatValue *buf = malloc((len > 0 ? len : 1) * sizeof(LatValue)); |
770 | 12 | for (size_t i = 0; i < len; i++) |
771 | 10 | buf[i] = rvm_clone(&obj->as.array.elems[i]); |
772 | | /* Insertion sort using comparator: closure(a, b) < 0 means a < b */ |
773 | 10 | for (size_t i = 1; i < len; i++) { |
774 | 8 | LatValue key = buf[i]; |
775 | 8 | size_t j = i; |
776 | 17 | while (j > 0) { |
777 | 14 | LatValue ca[2]; |
778 | 14 | ca[0] = rvm_clone(&key); |
779 | 14 | ca[1] = rvm_clone(&buf[j - 1]); |
780 | 14 | LatValue cmp = regvm_call_closure(vm, closure, ca, 2); |
781 | 14 | value_free(&ca[0]); value_free(&ca[1]); |
782 | 14 | if (cmp.type != VAL_INT || cmp.as.int_val >= 0) { value_free(&cmp); break; } |
783 | 9 | value_free(&cmp); |
784 | 9 | buf[j] = buf[j - 1]; j--; |
785 | 9 | } |
786 | 8 | buf[j] = key; |
787 | 8 | } |
788 | 2 | *result = value_array(buf, len); |
789 | 2 | free(buf); |
790 | 2 | return true; |
791 | 2 | } |
792 | 56 | if (strcmp(method, "find") == 0 && arg_count == 1) { |
793 | 2 | LatValue *closure = &args[0]; |
794 | 8 | for (size_t i = 0; i < obj->as.array.len; i++) { |
795 | 7 | LatValue arg = rvm_clone(&obj->as.array.elems[i]); |
796 | 7 | LatValue pred = regvm_call_closure(vm, closure, &arg, 1); |
797 | 7 | bool found = pred.type == VAL_BOOL && pred.as.bool_val; |
798 | 7 | value_free(&pred); |
799 | 7 | if (found) { *result = arg; return true; } |
800 | 6 | value_free(&arg); |
801 | 6 | } |
802 | 1 | *result = value_unit(); |
803 | 1 | return true; |
804 | 2 | } |
805 | 54 | if (strcmp(method, "any") == 0 && arg_count == 1) { |
806 | 2 | LatValue *closure = &args[0]; |
807 | 7 | for (size_t i = 0; i < obj->as.array.len; i++) { |
808 | 6 | LatValue arg = rvm_clone(&obj->as.array.elems[i]); |
809 | 6 | LatValue pred = regvm_call_closure(vm, closure, &arg, 1); |
810 | 6 | bool yes = pred.type == VAL_BOOL && pred.as.bool_val; |
811 | 6 | value_free(&pred); |
812 | 6 | value_free(&arg); |
813 | 6 | if (yes) { *result = value_bool(true); return true; } |
814 | 6 | } |
815 | 1 | *result = value_bool(false); |
816 | 1 | return true; |
817 | 2 | } |
818 | 52 | if (strcmp(method, "all") == 0 && arg_count == 1) { |
819 | 2 | LatValue *closure = &args[0]; |
820 | 5 | for (size_t i = 0; i < obj->as.array.len; i++) { |
821 | 4 | LatValue arg = rvm_clone(&obj->as.array.elems[i]); |
822 | 4 | LatValue pred = regvm_call_closure(vm, closure, &arg, 1); |
823 | 4 | bool yes = pred.type == VAL_BOOL && pred.as.bool_val; |
824 | 4 | value_free(&pred); |
825 | 4 | value_free(&arg); |
826 | 4 | if (!yes) { *result = value_bool(false); return true; } |
827 | 4 | } |
828 | 1 | *result = value_bool(true); |
829 | 1 | return true; |
830 | 2 | } |
831 | 50 | if (strcmp(method, "flat_map") == 0 && arg_count == 1) { |
832 | 3 | LatValue *closure = &args[0]; |
833 | 3 | size_t cap = obj->as.array.len * 2; |
834 | 3 | LatValue *elems = malloc(cap * sizeof(LatValue)); |
835 | 3 | size_t out = 0; |
836 | 9 | for (size_t i = 0; i < obj->as.array.len; i++) { |
837 | 6 | LatValue arg = rvm_clone(&obj->as.array.elems[i]); |
838 | 6 | LatValue mapped = regvm_call_closure(vm, closure, &arg, 1); |
839 | 6 | value_free(&arg); |
840 | 6 | if (mapped.type == VAL_ARRAY) { |
841 | 9 | for (size_t j = 0; j < mapped.as.array.len; j++) { |
842 | 6 | if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); } |
843 | 6 | elems[out++] = rvm_clone(&mapped.as.array.elems[j]); |
844 | 6 | } |
845 | 3 | value_free(&mapped); |
846 | 3 | } else { |
847 | 3 | if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); } |
848 | 3 | elems[out++] = mapped; |
849 | 3 | } |
850 | 6 | } |
851 | 3 | *result = value_array(elems, out); |
852 | 3 | free(elems); |
853 | 3 | return true; |
854 | 3 | } |
855 | 47 | if (strcmp(method, "unique") == 0 && arg_count == 0) { |
856 | 1 | size_t len = obj->as.array.len; |
857 | 1 | LatValue *elems = malloc(len * sizeof(LatValue)); |
858 | 1 | size_t out = 0; |
859 | 7 | for (size_t i = 0; i < len; i++) { |
860 | 6 | bool dup = false; |
861 | 13 | for (size_t j = 0; j < out; j++) { |
862 | 9 | if (value_eq(&obj->as.array.elems[i], &elems[j])) { dup = true; break; } |
863 | 9 | } |
864 | 6 | if (!dup) elems[out++] = rvm_clone(&obj->as.array.elems[i]); |
865 | 6 | } |
866 | 1 | *result = value_array(elems, out); |
867 | 1 | free(elems); |
868 | 1 | return true; |
869 | 1 | } |
870 | 46 | if (strcmp(method, "index_of") == 0 && arg_count == 1) { |
871 | 6 | for (size_t i = 0; i < obj->as.array.len; i++) { |
872 | 5 | if (value_eq(&obj->as.array.elems[i], &args[0])) { |
873 | 1 | *result = value_int((int64_t)i); |
874 | 1 | return true; |
875 | 1 | } |
876 | 5 | } |
877 | 1 | *result = value_int(-1); |
878 | 1 | return true; |
879 | 2 | } |
880 | 44 | if (strcmp(method, "first") == 0 && arg_count == 0) { |
881 | 9 | *result = obj->as.array.len > 0 ? rvm_clone(&obj->as.array.elems[0]) : value_unit(); |
882 | 9 | return true; |
883 | 9 | } |
884 | 35 | if (strcmp(method, "last") == 0 && arg_count == 0) { |
885 | 3 | *result = obj->as.array.len > 0 ? rvm_clone(&obj->as.array.elems[obj->as.array.len - 1]) : value_unit(); |
886 | 3 | return true; |
887 | 3 | } |
888 | 32 | if (strcmp(method, "slice") == 0 && (arg_count == 1 || arg_count == 2)) { |
889 | 4 | int64_t start = args[0].type == VAL_INT ? args[0].as.int_val : 0; |
890 | 4 | int64_t end = arg_count == 2 && args[1].type == VAL_INT ? args[1].as.int_val : (int64_t)obj->as.array.len; |
891 | 4 | if (start < 0) start += (int64_t)obj->as.array.len; |
892 | 4 | if (end < 0) end += (int64_t)obj->as.array.len; |
893 | 4 | if (start < 0) start = 0; |
894 | 4 | if (end > (int64_t)obj->as.array.len) end = (int64_t)obj->as.array.len; |
895 | 4 | if (start >= end) { *result = value_array(NULL, 0); return true; } |
896 | 3 | size_t count = (size_t)(end - start); |
897 | 3 | LatValue *elems = malloc(count * sizeof(LatValue)); |
898 | 11 | for (size_t i = 0; i < count; i++) |
899 | 8 | elems[i] = rvm_clone(&obj->as.array.elems[start + (int64_t)i]); |
900 | 3 | *result = value_array(elems, count); |
901 | 3 | free(elems); |
902 | 3 | return true; |
903 | 4 | } |
904 | 28 | if (strcmp(method, "take") == 0 && arg_count == 1) { |
905 | 3 | int64_t n = args[0].type == VAL_INT ? args[0].as.int_val : 0; |
906 | 3 | if (n < 0) n = 0; |
907 | 3 | if (n > (int64_t)obj->as.array.len) n = (int64_t)obj->as.array.len; |
908 | 3 | LatValue *elems = malloc((size_t)n * sizeof(LatValue)); |
909 | 8 | for (int64_t i = 0; i < n; i++) |
910 | 5 | elems[i] = rvm_clone(&obj->as.array.elems[i]); |
911 | 3 | *result = value_array(elems, (size_t)n); |
912 | 3 | free(elems); |
913 | 3 | return true; |
914 | 3 | } |
915 | 25 | if (strcmp(method, "drop") == 0 && arg_count == 1) { |
916 | 3 | int64_t n = args[0].type == VAL_INT ? args[0].as.int_val : 0; |
917 | 3 | if (n < 0) n = 0; |
918 | 3 | if (n > (int64_t)obj->as.array.len) n = (int64_t)obj->as.array.len; |
919 | 3 | size_t count = obj->as.array.len - (size_t)n; |
920 | 3 | LatValue *elems = malloc(count * sizeof(LatValue)); |
921 | 9 | for (size_t i = 0; i < count; i++) |
922 | 6 | elems[i] = rvm_clone(&obj->as.array.elems[n + (int64_t)i]); |
923 | 3 | *result = value_array(elems, count); |
924 | 3 | free(elems); |
925 | 3 | return true; |
926 | 3 | } |
927 | 22 | if (strcmp(method, "flatten") == 0 && arg_count == 0) { |
928 | 0 | size_t cap = obj->as.array.len * 2; |
929 | 0 | LatValue *elems = malloc(cap * sizeof(LatValue)); |
930 | 0 | size_t out = 0; |
931 | 0 | for (size_t i = 0; i < obj->as.array.len; i++) { |
932 | 0 | if (obj->as.array.elems[i].type == VAL_ARRAY) { |
933 | 0 | LatValue *inner = &obj->as.array.elems[i]; |
934 | 0 | for (size_t j = 0; j < inner->as.array.len; j++) { |
935 | 0 | if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); } |
936 | 0 | elems[out++] = rvm_clone(&inner->as.array.elems[j]); |
937 | 0 | } |
938 | 0 | } else { |
939 | 0 | if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); } |
940 | 0 | elems[out++] = rvm_clone(&obj->as.array.elems[i]); |
941 | 0 | } |
942 | 0 | } |
943 | 0 | *result = value_array(elems, out); |
944 | 0 | free(elems); |
945 | 0 | return true; |
946 | 0 | } |
947 | 22 | if (strcmp(method, "zip") == 0 && arg_count == 1) { |
948 | 1 | if (args[0].type != VAL_ARRAY) { *result = value_array(NULL, 0); return true; } |
949 | 1 | size_t len = obj->as.array.len < args[0].as.array.len ? obj->as.array.len : args[0].as.array.len; |
950 | 1 | LatValue *elems = malloc(len * sizeof(LatValue)); |
951 | 3 | for (size_t i = 0; i < len; i++) { |
952 | 2 | LatValue pair[2]; |
953 | 2 | pair[0] = rvm_clone(&obj->as.array.elems[i]); |
954 | 2 | pair[1] = rvm_clone(&args[0].as.array.elems[i]); |
955 | 2 | elems[i] = value_array(pair, 2); |
956 | 2 | } |
957 | 1 | *result = value_array(elems, len); |
958 | 1 | free(elems); |
959 | 1 | return true; |
960 | 1 | } |
961 | 21 | if (strcmp(method, "sum") == 0 && arg_count == 0) { |
962 | 3 | int64_t isum = 0; double fsum = 0; bool has_float = false; |
963 | 11 | for (size_t i = 0; i < obj->as.array.len; i++) { |
964 | 8 | if (obj->as.array.elems[i].type == VAL_FLOAT) { has_float = true; fsum += obj->as.array.elems[i].as.float_val; } |
965 | 5 | else if (obj->as.array.elems[i].type == VAL_INT) { isum += obj->as.array.elems[i].as.int_val; fsum += (double)obj->as.array.elems[i].as.int_val; } |
966 | 8 | } |
967 | 3 | *result = has_float ? value_float(fsum) : value_int(isum); |
968 | 3 | return true; |
969 | 3 | } |
970 | 18 | if (strcmp(method, "min") == 0 && arg_count == 0) { |
971 | 3 | if (obj->as.array.len == 0) { |
972 | 1 | vm->error = strdup("min() called on empty array"); |
973 | 1 | *result = value_unit(); |
974 | 1 | return true; |
975 | 1 | } |
976 | 2 | LatValue min_val = obj->as.array.elems[0]; |
977 | 8 | for (size_t i = 1; i < obj->as.array.len; i++) { |
978 | 6 | if (obj->as.array.elems[i].type == VAL_INT && min_val.type == VAL_INT) { |
979 | 4 | if (obj->as.array.elems[i].as.int_val < min_val.as.int_val) min_val = obj->as.array.elems[i]; |
980 | 4 | } else if (obj->as.array.elems[i].type == VAL_FLOAT || min_val.type == VAL_FLOAT) { |
981 | 2 | double a = obj->as.array.elems[i].type == VAL_FLOAT ? obj->as.array.elems[i].as.float_val : (double)obj->as.array.elems[i].as.int_val; |
982 | 2 | double b = min_val.type == VAL_FLOAT ? min_val.as.float_val : (double)min_val.as.int_val; |
983 | 2 | if (a < b) min_val = obj->as.array.elems[i]; |
984 | 2 | } |
985 | 6 | } |
986 | 2 | *result = rvm_clone(&min_val); |
987 | 2 | return true; |
988 | 3 | } |
989 | 15 | if (strcmp(method, "max") == 0 && arg_count == 0) { |
990 | 3 | if (obj->as.array.len == 0) { |
991 | 1 | vm->error = strdup("max() called on empty array"); |
992 | 1 | *result = value_unit(); |
993 | 1 | return true; |
994 | 1 | } |
995 | 2 | LatValue max_val = obj->as.array.elems[0]; |
996 | 8 | for (size_t i = 1; i < obj->as.array.len; i++) { |
997 | 6 | if (obj->as.array.elems[i].type == VAL_INT && max_val.type == VAL_INT) { |
998 | 4 | if (obj->as.array.elems[i].as.int_val > max_val.as.int_val) max_val = obj->as.array.elems[i]; |
999 | 4 | } else if (obj->as.array.elems[i].type == VAL_FLOAT || max_val.type == VAL_FLOAT) { |
1000 | 2 | double a = obj->as.array.elems[i].type == VAL_FLOAT ? obj->as.array.elems[i].as.float_val : (double)obj->as.array.elems[i].as.int_val; |
1001 | 2 | double b = max_val.type == VAL_FLOAT ? max_val.as.float_val : (double)max_val.as.int_val; |
1002 | 2 | if (a > b) max_val = obj->as.array.elems[i]; |
1003 | 2 | } |
1004 | 6 | } |
1005 | 2 | *result = rvm_clone(&max_val); |
1006 | 2 | return true; |
1007 | 3 | } |
1008 | 12 | if (strcmp(method, "insert") == 0 && arg_count == 2) { |
1009 | 1 | if (args[0].type != VAL_INT) { *result = value_unit(); return true; } |
1010 | 1 | int64_t idx = args[0].as.int_val; |
1011 | 1 | if (idx < 0) idx += (int64_t)obj->as.array.len; |
1012 | 1 | if (idx < 0) idx = 0; |
1013 | 1 | if (idx > (int64_t)obj->as.array.len) idx = (int64_t)obj->as.array.len; |
1014 | 1 | if (obj->as.array.len >= obj->as.array.cap) { |
1015 | 0 | obj->as.array.cap = obj->as.array.cap ? obj->as.array.cap * 2 : 4; |
1016 | 0 | obj->as.array.elems = realloc(obj->as.array.elems, obj->as.array.cap * sizeof(LatValue)); |
1017 | 0 | } |
1018 | 1 | memmove(&obj->as.array.elems[idx + 1], &obj->as.array.elems[idx], |
1019 | 1 | (obj->as.array.len - (size_t)idx) * sizeof(LatValue)); |
1020 | 1 | obj->as.array.elems[idx] = rvm_clone(&args[1]); |
1021 | 1 | obj->as.array.len++; |
1022 | 1 | *result = value_unit(); |
1023 | 1 | return true; |
1024 | 1 | } |
1025 | 11 | if (strcmp(method, "remove_at") == 0 && arg_count == 1) { |
1026 | 1 | if (args[0].type != VAL_INT) { *result = value_nil(); return true; } |
1027 | 1 | int64_t idx = args[0].as.int_val; |
1028 | 1 | if (idx < 0) idx += (int64_t)obj->as.array.len; |
1029 | 1 | if (idx < 0 || (size_t)idx >= obj->as.array.len) { *result = value_nil(); return true; } |
1030 | 1 | *result = obj->as.array.elems[idx]; |
1031 | 1 | memmove(&obj->as.array.elems[idx], &obj->as.array.elems[idx + 1], |
1032 | 1 | (obj->as.array.len - (size_t)idx - 1) * sizeof(LatValue)); |
1033 | 1 | obj->as.array.len--; |
1034 | 1 | return true; |
1035 | 1 | } |
1036 | 10 | if (strcmp(method, "chunk") == 0 && arg_count == 1) { |
1037 | 5 | if (args[0].type != VAL_INT || args[0].as.int_val <= 0) { *result = value_array(NULL, 0); return true; } |
1038 | 5 | int64_t sz = args[0].as.int_val; |
1039 | 5 | size_t len = obj->as.array.len; |
1040 | 5 | size_t chunks = (len + (size_t)sz - 1) / (size_t)sz; |
1041 | 5 | LatValue *elems = malloc(chunks * sizeof(LatValue)); |
1042 | 14 | for (size_t i = 0; i < chunks; i++) { |
1043 | 9 | size_t start = i * (size_t)sz; |
1044 | 9 | size_t end = start + (size_t)sz; |
1045 | 9 | if (end > len) end = len; |
1046 | 9 | size_t count = end - start; |
1047 | 9 | LatValue *chunk_elems = malloc(count * sizeof(LatValue)); |
1048 | 25 | for (size_t j = 0; j < count; j++) |
1049 | 16 | chunk_elems[j] = rvm_clone(&obj->as.array.elems[start + j]); |
1050 | 9 | elems[i] = value_array(chunk_elems, count); |
1051 | 9 | free(chunk_elems); |
1052 | 9 | } |
1053 | 5 | *result = value_array(elems, chunks); |
1054 | 5 | free(elems); |
1055 | 5 | return true; |
1056 | 5 | } |
1057 | 5 | if (strcmp(method, "group_by") == 0 && arg_count == 1) { |
1058 | 1 | LatValue *closure = &args[0]; |
1059 | 1 | LatValue map = value_map_new(); |
1060 | 6 | for (size_t i = 0; i < obj->as.array.len; i++) { |
1061 | 5 | LatValue arg = rvm_clone(&obj->as.array.elems[i]); |
1062 | 5 | LatValue key = regvm_call_closure(vm, closure, &arg, 1); |
1063 | 5 | char *key_str = value_display(&key); |
1064 | 5 | LatValue *existing = lat_map_get(map.as.map.map, key_str); |
1065 | 5 | if (existing && existing->type == VAL_ARRAY) { |
1066 | 3 | if (existing->as.array.len >= existing->as.array.cap) { |
1067 | 0 | existing->as.array.cap = existing->as.array.cap ? existing->as.array.cap * 2 : 4; |
1068 | 0 | existing->as.array.elems = realloc(existing->as.array.elems, existing->as.array.cap * sizeof(LatValue)); |
1069 | 0 | } |
1070 | 3 | existing->as.array.elems[existing->as.array.len++] = arg; |
1071 | 3 | } else { |
1072 | 2 | LatValue arr = value_array(&arg, 1); |
1073 | 2 | lat_map_set(map.as.map.map, key_str, &arr); |
1074 | 2 | } |
1075 | 5 | value_free(&key); |
1076 | 5 | free(key_str); |
1077 | 5 | } |
1078 | 1 | *result = map; |
1079 | 1 | return true; |
1080 | 1 | } |
1081 | 5 | } |
1082 | | |
1083 | | /* ── Array additional methods ── */ |
1084 | 467 | if (obj->type == VAL_ARRAY) { |
1085 | 4 | if (strcmp(method, "flat") == 0 && arg_count == 0) { |
1086 | 4 | size_t cap = obj->as.array.len * 2; |
1087 | 4 | if (cap == 0) cap = 1; |
1088 | 4 | LatValue *elems = malloc(cap * sizeof(LatValue)); |
1089 | 4 | size_t out = 0; |
1090 | 13 | for (size_t i = 0; i < obj->as.array.len; i++) { |
1091 | 9 | LatValue *el = &obj->as.array.elems[i]; |
1092 | 9 | if (el->type == VAL_ARRAY) { |
1093 | 13 | for (size_t j = 0; j < el->as.array.len; j++) { |
1094 | 8 | if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); } |
1095 | 8 | elems[out++] = rvm_clone(&el->as.array.elems[j]); |
1096 | 8 | } |
1097 | 5 | } else { |
1098 | 4 | if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); } |
1099 | 4 | elems[out++] = rvm_clone(el); |
1100 | 4 | } |
1101 | 9 | } |
1102 | 4 | *result = value_array(elems, out); |
1103 | 4 | free(elems); |
1104 | 4 | return true; |
1105 | 4 | } |
1106 | 0 | if (strcmp(method, "first") == 0 && arg_count == 0) { |
1107 | 0 | *result = (obj->as.array.len > 0) ? rvm_clone(&obj->as.array.elems[0]) : value_unit(); |
1108 | 0 | return true; |
1109 | 0 | } |
1110 | 0 | if (strcmp(method, "last") == 0 && arg_count == 0) { |
1111 | 0 | *result = (obj->as.array.len > 0) ? rvm_clone(&obj->as.array.elems[obj->as.array.len - 1]) : value_unit(); |
1112 | 0 | return true; |
1113 | 0 | } |
1114 | 0 | if (strcmp(method, "min") == 0 && arg_count == 0) { |
1115 | 0 | if (obj->as.array.len == 0) { *result = value_unit(); return true; } |
1116 | 0 | LatValue best = rvm_clone(&obj->as.array.elems[0]); |
1117 | 0 | for (size_t i = 1; i < obj->as.array.len; i++) { |
1118 | 0 | LatValue *el = &obj->as.array.elems[i]; |
1119 | 0 | bool less = false; |
1120 | 0 | if (el->type == VAL_INT && best.type == VAL_INT) |
1121 | 0 | less = el->as.int_val < best.as.int_val; |
1122 | 0 | else if (el->type == VAL_FLOAT || best.type == VAL_FLOAT) { |
1123 | 0 | double a = el->type == VAL_FLOAT ? el->as.float_val : (double)el->as.int_val; |
1124 | 0 | double b = best.type == VAL_FLOAT ? best.as.float_val : (double)best.as.int_val; |
1125 | 0 | less = a < b; |
1126 | 0 | } |
1127 | 0 | if (less) { value_free(&best); best = rvm_clone(el); } |
1128 | 0 | } |
1129 | 0 | *result = best; |
1130 | 0 | return true; |
1131 | 0 | } |
1132 | 0 | if (strcmp(method, "max") == 0 && arg_count == 0) { |
1133 | 0 | if (obj->as.array.len == 0) { *result = value_unit(); return true; } |
1134 | 0 | LatValue best = rvm_clone(&obj->as.array.elems[0]); |
1135 | 0 | for (size_t i = 1; i < obj->as.array.len; i++) { |
1136 | 0 | LatValue *el = &obj->as.array.elems[i]; |
1137 | 0 | bool greater = false; |
1138 | 0 | if (el->type == VAL_INT && best.type == VAL_INT) |
1139 | 0 | greater = el->as.int_val > best.as.int_val; |
1140 | 0 | else if (el->type == VAL_FLOAT || best.type == VAL_FLOAT) { |
1141 | 0 | double a = el->type == VAL_FLOAT ? el->as.float_val : (double)el->as.int_val; |
1142 | 0 | double b = best.type == VAL_FLOAT ? best.as.float_val : (double)best.as.int_val; |
1143 | 0 | greater = a > b; |
1144 | 0 | } |
1145 | 0 | if (greater) { value_free(&best); best = rvm_clone(el); } |
1146 | 0 | } |
1147 | 0 | *result = best; |
1148 | 0 | return true; |
1149 | 0 | } |
1150 | 0 | } |
1151 | | |
1152 | | /* ── String additional methods ── */ |
1153 | 463 | if (obj->type == VAL_STR) { |
1154 | 186 | if (strcmp(method, "split") == 0 && arg_count == 1) { |
1155 | 11 | if (args[0].type != VAL_STR) { *result = value_array(NULL, 0); return true; } |
1156 | 11 | const char *s = obj->as.str_val; |
1157 | 11 | const char *sep = args[0].as.str_val; |
1158 | 11 | size_t sep_len = strlen(sep); |
1159 | 11 | size_t cap = 8; |
1160 | 11 | LatValue *parts = malloc(cap * sizeof(LatValue)); |
1161 | 11 | size_t count = 0; |
1162 | 11 | if (sep_len == 0) { |
1163 | 0 | for (size_t i = 0; s[i]; i++) { |
1164 | 0 | if (count >= cap) { cap *= 2; parts = realloc(parts, cap * sizeof(LatValue)); } |
1165 | 0 | char c[2] = { s[i], '\0' }; |
1166 | 0 | parts[count++] = value_string(c); |
1167 | 0 | } |
1168 | 11 | } else { |
1169 | 11 | const char *p = s; |
1170 | 16 | while (*p) { |
1171 | 15 | const char *found = strstr(p, sep); |
1172 | 15 | if (!found) { if (count >= cap) { cap *= 2; parts = realloc(parts, cap * sizeof(LatValue)); } parts[count++] = value_string(p); break; } |
1173 | 5 | if (count >= cap) { cap *= 2; parts = realloc(parts, cap * sizeof(LatValue)); } |
1174 | 5 | char *part = strndup(p, (size_t)(found - p)); |
1175 | 5 | parts[count++] = value_string_owned(part); |
1176 | 5 | p = found + sep_len; |
1177 | 5 | } |
1178 | 11 | } |
1179 | 11 | *result = value_array(parts, count); |
1180 | 11 | free(parts); |
1181 | 11 | return true; |
1182 | 11 | } |
1183 | 175 | if (strcmp(method, "trim") == 0 && arg_count == 0) { |
1184 | 42 | const char *s = obj->as.str_val; |
1185 | 45 | while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++; |
1186 | 42 | const char *e = obj->as.str_val + strlen(obj->as.str_val); |
1187 | 49 | while (e > s && (*(e-1) == ' ' || *(e-1) == '\t' || *(e-1) == '\n' || *(e-1) == '\r')) e--; |
1188 | 42 | *result = value_string_owned(strndup(s, (size_t)(e - s))); |
1189 | 42 | return true; |
1190 | 42 | } |
1191 | 133 | if (strcmp(method, "trim_start") == 0 && arg_count == 0) { |
1192 | 1 | const char *s = obj->as.str_val; |
1193 | 3 | while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++; |
1194 | 1 | *result = value_string(s); |
1195 | 1 | return true; |
1196 | 1 | } |
1197 | 132 | if (strcmp(method, "trim_end") == 0 && arg_count == 0) { |
1198 | 1 | size_t len = strlen(obj->as.str_val); |
1199 | 1 | const char *e = obj->as.str_val + len; |
1200 | 3 | while (e > obj->as.str_val && (*(e-1) == ' ' || *(e-1) == '\t' || *(e-1) == '\n' || *(e-1) == '\r')) e--; |
1201 | 1 | *result = value_string_owned(strndup(obj->as.str_val, (size_t)(e - obj->as.str_val))); |
1202 | 1 | return true; |
1203 | 1 | } |
1204 | 131 | if (strcmp(method, "to_upper") == 0 && arg_count == 0) { |
1205 | 3 | char *s = strdup(obj->as.str_val); |
1206 | 24 | for (char *p = s; *p; p++) if (*p >= 'a' && *p <= 'z') *p -= 32; |
1207 | 3 | *result = value_string_owned(s); |
1208 | 3 | return true; |
1209 | 3 | } |
1210 | 128 | if (strcmp(method, "to_lower") == 0 && arg_count == 0) { |
1211 | 2 | char *s = strdup(obj->as.str_val); |
1212 | 18 | for (char *p = s; *p; p++) if (*p >= 'A' && *p <= 'Z') *p += 32; |
1213 | 2 | *result = value_string_owned(s); |
1214 | 2 | return true; |
1215 | 2 | } |
1216 | 126 | if (strcmp(method, "starts_with") == 0 && arg_count == 1) { |
1217 | 45 | if (args[0].type == VAL_STR) |
1218 | 45 | *result = value_bool(strncmp(obj->as.str_val, args[0].as.str_val, strlen(args[0].as.str_val)) == 0); |
1219 | 0 | else |
1220 | 0 | *result = value_bool(false); |
1221 | 45 | return true; |
1222 | 45 | } |
1223 | 81 | if (strcmp(method, "ends_with") == 0 && arg_count == 1) { |
1224 | 2 | if (args[0].type == VAL_STR) { |
1225 | 2 | size_t slen = strlen(obj->as.str_val); |
1226 | 2 | size_t plen = strlen(args[0].as.str_val); |
1227 | 2 | *result = value_bool(plen <= slen && strcmp(obj->as.str_val + slen - plen, args[0].as.str_val) == 0); |
1228 | 2 | } else { |
1229 | 0 | *result = value_bool(false); |
1230 | 0 | } |
1231 | 2 | return true; |
1232 | 2 | } |
1233 | 79 | if (strcmp(method, "replace") == 0 && arg_count == 2) { |
1234 | 2 | if (args[0].type != VAL_STR || args[1].type != VAL_STR) { *result = rvm_clone(obj); return true; } |
1235 | 2 | const char *s = obj->as.str_val; |
1236 | 2 | const char *from = args[0].as.str_val; |
1237 | 2 | const char *to = args[1].as.str_val; |
1238 | 2 | size_t from_len = strlen(from), to_len = strlen(to); |
1239 | 2 | if (from_len == 0) { *result = rvm_clone(obj); return true; } |
1240 | 2 | size_t cap = strlen(s) + 64; |
1241 | 2 | char *buf = malloc(cap); |
1242 | 2 | size_t pos = 0; |
1243 | 14 | while (*s) { |
1244 | 12 | if (strncmp(s, from, from_len) == 0) { |
1245 | 5 | while (pos + to_len >= cap) { cap *= 2; buf = realloc(buf, cap); } |
1246 | 5 | memcpy(buf + pos, to, to_len); pos += to_len; s += from_len; |
1247 | 7 | } else { |
1248 | 7 | if (pos + 1 >= cap) { cap *= 2; buf = realloc(buf, cap); } |
1249 | 7 | buf[pos++] = *s++; |
1250 | 7 | } |
1251 | 12 | } |
1252 | 2 | buf[pos] = '\0'; |
1253 | 2 | *result = value_string_owned(buf); |
1254 | 2 | return true; |
1255 | 2 | } |
1256 | 77 | if (strcmp(method, "index_of") == 0 && arg_count == 1) { |
1257 | 27 | if (args[0].type == VAL_STR) { |
1258 | 27 | const char *found = strstr(obj->as.str_val, args[0].as.str_val); |
1259 | 27 | *result = found ? value_int((int64_t)(found - obj->as.str_val)) : value_int(-1); |
1260 | 27 | } else { |
1261 | 0 | *result = value_int(-1); |
1262 | 0 | } |
1263 | 27 | return true; |
1264 | 27 | } |
1265 | 50 | if (strcmp(method, "substring") == 0 && (arg_count == 1 || arg_count == 2)) { |
1266 | 31 | size_t slen = strlen(obj->as.str_val); |
1267 | 31 | int64_t start = args[0].type == VAL_INT ? args[0].as.int_val : 0; |
1268 | 31 | int64_t end = arg_count == 2 && args[1].type == VAL_INT ? args[1].as.int_val : (int64_t)slen; |
1269 | 31 | if (start < 0) start += (int64_t)slen; |
1270 | 31 | if (end < 0) end += (int64_t)slen; |
1271 | 31 | if (start < 0) start = 0; |
1272 | 31 | if (end > (int64_t)slen) end = (int64_t)slen; |
1273 | 31 | if (start >= end) { *result = value_string(""); return true; } |
1274 | 31 | *result = value_string_owned(strndup(obj->as.str_val + start, (size_t)(end - start))); |
1275 | 31 | return true; |
1276 | 31 | } |
1277 | 19 | if (strcmp(method, "repeat") == 0 && arg_count == 1) { |
1278 | 2 | if (args[0].type != VAL_INT || args[0].as.int_val < 0) { *result = value_string(""); return true; } |
1279 | 2 | int64_t n = args[0].as.int_val; |
1280 | 2 | size_t slen = strlen(obj->as.str_val); |
1281 | 2 | char *buf = malloc(slen * (size_t)n + 1); |
1282 | 5 | for (int64_t i = 0; i < n; i++) |
1283 | 3 | memcpy(buf + i * (int64_t)slen, obj->as.str_val, slen); |
1284 | 2 | buf[slen * (size_t)n] = '\0'; |
1285 | 2 | *result = value_string_owned(buf); |
1286 | 2 | return true; |
1287 | 2 | } |
1288 | 17 | if (strcmp(method, "chars") == 0 && arg_count == 0) { |
1289 | 7 | size_t len = strlen(obj->as.str_val); |
1290 | 7 | LatValue *elems = malloc(len * sizeof(LatValue)); |
1291 | 94 | for (size_t i = 0; i < len; i++) { |
1292 | 87 | char c[2] = { obj->as.str_val[i], '\0' }; |
1293 | 87 | elems[i] = value_string(c); |
1294 | 87 | } |
1295 | 7 | *result = value_array(elems, len); |
1296 | 7 | free(elems); |
1297 | 7 | return true; |
1298 | 7 | } |
1299 | 10 | if (strcmp(method, "bytes") == 0 && arg_count == 0) { |
1300 | 1 | size_t len = strlen(obj->as.str_val); |
1301 | 1 | LatValue *elems = malloc(len * sizeof(LatValue)); |
1302 | 4 | for (size_t i = 0; i < len; i++) |
1303 | 3 | elems[i] = value_int((int64_t)(unsigned char)obj->as.str_val[i]); |
1304 | 1 | *result = value_array(elems, len); |
1305 | 1 | free(elems); |
1306 | 1 | return true; |
1307 | 1 | } |
1308 | 9 | if (strcmp(method, "reverse") == 0 && arg_count == 0) { |
1309 | 2 | size_t len = strlen(obj->as.str_val); |
1310 | 2 | char *buf = malloc(len + 1); |
1311 | 7 | for (size_t i = 0; i < len; i++) buf[i] = obj->as.str_val[len - 1 - i]; |
1312 | 2 | buf[len] = '\0'; |
1313 | 2 | *result = value_string_owned(buf); |
1314 | 2 | return true; |
1315 | 2 | } |
1316 | 7 | if (strcmp(method, "pad_left") == 0 && (arg_count == 1 || arg_count == 2)) { |
1317 | 1 | int64_t n = args[0].type == VAL_INT ? args[0].as.int_val : 0; |
1318 | 1 | char pad = (arg_count == 2 && args[1].type == VAL_STR && args[1].as.str_val[0]) ? args[1].as.str_val[0] : ' '; |
1319 | 1 | size_t slen = strlen(obj->as.str_val); |
1320 | 1 | if ((int64_t)slen >= n) { *result = rvm_clone(obj); return true; } |
1321 | 1 | size_t plen = (size_t)n - slen; |
1322 | 1 | char *buf = malloc((size_t)n + 1); |
1323 | 1 | memset(buf, pad, plen); |
1324 | 1 | memcpy(buf + plen, obj->as.str_val, slen); |
1325 | 1 | buf[(size_t)n] = '\0'; |
1326 | 1 | *result = value_string_owned(buf); |
1327 | 1 | return true; |
1328 | 1 | } |
1329 | 6 | if (strcmp(method, "pad_right") == 0 && (arg_count == 1 || arg_count == 2)) { |
1330 | 1 | int64_t n = args[0].type == VAL_INT ? args[0].as.int_val : 0; |
1331 | 1 | char pad = (arg_count == 2 && args[1].type == VAL_STR && args[1].as.str_val[0]) ? args[1].as.str_val[0] : ' '; |
1332 | 1 | size_t slen = strlen(obj->as.str_val); |
1333 | 1 | if ((int64_t)slen >= n) { *result = rvm_clone(obj); return true; } |
1334 | 1 | char *buf = malloc((size_t)n + 1); |
1335 | 1 | memcpy(buf, obj->as.str_val, slen); |
1336 | 1 | memset(buf + slen, pad, (size_t)n - slen); |
1337 | 1 | buf[(size_t)n] = '\0'; |
1338 | 1 | *result = value_string_owned(buf); |
1339 | 1 | return true; |
1340 | 1 | } |
1341 | 6 | } |
1342 | | |
1343 | | /* ── Enum methods ── */ |
1344 | 282 | if (obj->type == VAL_ENUM) { |
1345 | 6 | if (strcmp(method, "tag") == 0 || strcmp(method, "variant_name") == 0) { |
1346 | 1 | *result = value_string(obj->as.enm.variant_name); |
1347 | 1 | return true; |
1348 | 1 | } |
1349 | 5 | if (strcmp(method, "enum_name") == 0) { |
1350 | 1 | *result = value_string(obj->as.enm.enum_name); |
1351 | 1 | return true; |
1352 | 1 | } |
1353 | 4 | if (strcmp(method, "payload") == 0) { |
1354 | 2 | if (obj->as.enm.payload_count > 0) { |
1355 | 2 | LatValue *elems = malloc(obj->as.enm.payload_count * sizeof(LatValue)); |
1356 | 5 | for (size_t pi = 0; pi < obj->as.enm.payload_count; pi++) |
1357 | 3 | elems[pi] = rvm_clone(&obj->as.enm.payload[pi]); |
1358 | 2 | *result = value_array(elems, obj->as.enm.payload_count); |
1359 | 2 | free(elems); |
1360 | 2 | } else { |
1361 | 0 | *result = value_array(NULL, 0); |
1362 | 0 | } |
1363 | 2 | return true; |
1364 | 2 | } |
1365 | 2 | if (strcmp(method, "is_variant") == 0 && arg_count == 1) { |
1366 | 2 | if (args[0].type == VAL_STR) |
1367 | 2 | *result = value_bool(strcmp(obj->as.enm.variant_name, args[0].as.str_val) == 0); |
1368 | 0 | else |
1369 | 0 | *result = value_bool(false); |
1370 | 2 | return true; |
1371 | 2 | } |
1372 | 2 | } |
1373 | | |
1374 | | /* ── Tuple methods ── */ |
1375 | 276 | if (obj->type == VAL_TUPLE) { |
1376 | 1 | if (strcmp(method, "len") == 0 && arg_count == 0) { |
1377 | 1 | *result = value_int((int64_t)obj->as.tuple.len); |
1378 | 1 | return true; |
1379 | 1 | } |
1380 | 1 | } |
1381 | | |
1382 | | /* ── Range methods ── */ |
1383 | 275 | if (obj->type == VAL_RANGE) { |
1384 | 0 | if (strcmp(method, "len") == 0 && arg_count == 0) { |
1385 | 0 | int64_t len = obj->as.range.end - obj->as.range.start; |
1386 | 0 | *result = value_int(len > 0 ? len : 0); |
1387 | 0 | return true; |
1388 | 0 | } |
1389 | 0 | if (strcmp(method, "contains") == 0 && arg_count == 1) { |
1390 | 0 | if (args[0].type == VAL_INT) { |
1391 | 0 | int64_t v = args[0].as.int_val; |
1392 | 0 | *result = value_bool(v >= obj->as.range.start && v < obj->as.range.end); |
1393 | 0 | } else { |
1394 | 0 | *result = value_bool(false); |
1395 | 0 | } |
1396 | 0 | return true; |
1397 | 0 | } |
1398 | 0 | } |
1399 | | |
1400 | | /* ── Set methods ── */ |
1401 | 275 | if (obj->type == VAL_SET) { |
1402 | 27 | if (strcmp(method, "len") == 0 && arg_count == 0) { |
1403 | 7 | *result = value_int((int64_t)lat_map_len(obj->as.set.map)); |
1404 | 7 | return true; |
1405 | 7 | } |
1406 | 20 | if (strcmp(method, "has") == 0 && arg_count == 1) { |
1407 | 7 | char *key = value_display(&args[0]); |
1408 | 7 | bool found = lat_map_contains(obj->as.set.map, key); |
1409 | 7 | free(key); |
1410 | 7 | *result = value_bool(found); |
1411 | 7 | return true; |
1412 | 7 | } |
1413 | 13 | if (strcmp(method, "add") == 0 && arg_count == 1) { |
1414 | 5 | char *key = value_display(&args[0]); |
1415 | 5 | LatValue val = rvm_clone(&args[0]); |
1416 | 5 | lat_map_set(obj->as.set.map, key, &val); |
1417 | 5 | free(key); |
1418 | 5 | *result = value_unit(); |
1419 | 5 | return true; |
1420 | 5 | } |
1421 | 8 | if (strcmp(method, "remove") == 0 && arg_count == 1) { |
1422 | 1 | char *key = value_display(&args[0]); |
1423 | 1 | lat_map_remove(obj->as.set.map, key); |
1424 | 1 | free(key); |
1425 | 1 | *result = value_unit(); |
1426 | 1 | return true; |
1427 | 1 | } |
1428 | 7 | if (strcmp(method, "to_array") == 0 && arg_count == 0) { |
1429 | 1 | size_t len = lat_map_len(obj->as.set.map); |
1430 | 1 | LatValue *elems = malloc((len > 0 ? len : 1) * sizeof(LatValue)); |
1431 | 1 | size_t idx = 0; |
1432 | 17 | for (size_t i = 0; i < obj->as.set.map->cap; i++) { |
1433 | 16 | if (obj->as.set.map->entries[i].state != MAP_OCCUPIED) continue; |
1434 | 1 | LatValue *v = (LatValue *)obj->as.set.map->entries[i].value; |
1435 | 1 | elems[idx++] = rvm_clone(v); |
1436 | 1 | } |
1437 | 1 | *result = value_array(elems, idx); |
1438 | 1 | free(elems); |
1439 | 1 | return true; |
1440 | 1 | } |
1441 | 6 | if (strcmp(method, "union") == 0 && arg_count == 1 && args[0].type == VAL_SET) { |
1442 | 1 | LatValue result_set = value_set_new(); |
1443 | 17 | for (size_t i = 0; i < obj->as.set.map->cap; i++) { |
1444 | 16 | if (obj->as.set.map->entries[i].state != MAP_OCCUPIED) continue; |
1445 | 2 | LatValue v = rvm_clone((LatValue *)obj->as.set.map->entries[i].value); |
1446 | 2 | lat_map_set(result_set.as.set.map, obj->as.set.map->entries[i].key, &v); |
1447 | 2 | } |
1448 | 17 | for (size_t i = 0; i < args[0].as.set.map->cap; i++) { |
1449 | 16 | if (args[0].as.set.map->entries[i].state != MAP_OCCUPIED) continue; |
1450 | 2 | LatValue v = rvm_clone((LatValue *)args[0].as.set.map->entries[i].value); |
1451 | 2 | lat_map_set(result_set.as.set.map, args[0].as.set.map->entries[i].key, &v); |
1452 | 2 | } |
1453 | 1 | *result = result_set; |
1454 | 1 | return true; |
1455 | 1 | } |
1456 | 5 | if (strcmp(method, "intersection") == 0 && arg_count == 1 && args[0].type == VAL_SET) { |
1457 | 1 | LatValue result_set = value_set_new(); |
1458 | 17 | for (size_t i = 0; i < obj->as.set.map->cap; i++) { |
1459 | 16 | if (obj->as.set.map->entries[i].state != MAP_OCCUPIED) continue; |
1460 | 3 | if (lat_map_contains(args[0].as.set.map, obj->as.set.map->entries[i].key)) { |
1461 | 2 | LatValue v = rvm_clone((LatValue *)obj->as.set.map->entries[i].value); |
1462 | 2 | lat_map_set(result_set.as.set.map, obj->as.set.map->entries[i].key, &v); |
1463 | 2 | } |
1464 | 3 | } |
1465 | 1 | *result = result_set; |
1466 | 1 | return true; |
1467 | 1 | } |
1468 | 4 | if (strcmp(method, "difference") == 0 && arg_count == 1 && args[0].type == VAL_SET) { |
1469 | 1 | LatValue result_set = value_set_new(); |
1470 | 17 | for (size_t i = 0; i < obj->as.set.map->cap; i++) { |
1471 | 16 | if (obj->as.set.map->entries[i].state != MAP_OCCUPIED) continue; |
1472 | 3 | if (!lat_map_contains(args[0].as.set.map, obj->as.set.map->entries[i].key)) { |
1473 | 1 | LatValue v = rvm_clone((LatValue *)obj->as.set.map->entries[i].value); |
1474 | 1 | lat_map_set(result_set.as.set.map, obj->as.set.map->entries[i].key, &v); |
1475 | 1 | } |
1476 | 3 | } |
1477 | 1 | *result = result_set; |
1478 | 1 | return true; |
1479 | 1 | } |
1480 | 3 | if (strcmp(method, "is_subset") == 0 && arg_count == 1 && args[0].type == VAL_SET) { |
1481 | 2 | bool is = true; |
1482 | 20 | for (size_t i = 0; i < obj->as.set.map->cap; i++) { |
1483 | 19 | if (obj->as.set.map->entries[i].state != MAP_OCCUPIED) continue; |
1484 | 3 | if (!lat_map_contains(args[0].as.set.map, obj->as.set.map->entries[i].key)) { |
1485 | 1 | is = false; break; |
1486 | 1 | } |
1487 | 3 | } |
1488 | 2 | *result = value_bool(is); |
1489 | 2 | return true; |
1490 | 2 | } |
1491 | 1 | if (strcmp(method, "is_superset") == 0 && arg_count == 1 && args[0].type == VAL_SET) { |
1492 | 1 | bool is = true; |
1493 | 17 | for (size_t i = 0; i < args[0].as.set.map->cap; i++) { |
1494 | 16 | if (args[0].as.set.map->entries[i].state != MAP_OCCUPIED) continue; |
1495 | 2 | if (!lat_map_contains(obj->as.set.map, args[0].as.set.map->entries[i].key)) { |
1496 | 0 | is = false; break; |
1497 | 0 | } |
1498 | 2 | } |
1499 | 1 | *result = value_bool(is); |
1500 | 1 | return true; |
1501 | 1 | } |
1502 | 1 | } |
1503 | | |
1504 | | /* ── String additional: count, is_empty ── */ |
1505 | 248 | if (obj->type == VAL_STR) { |
1506 | 5 | if (strcmp(method, "count") == 0 && arg_count == 1) { |
1507 | 3 | int64_t cnt = 0; |
1508 | 3 | if (args[0].type == VAL_STR && args[0].as.str_val[0]) { |
1509 | 3 | const char *p = obj->as.str_val; |
1510 | 3 | size_t nlen = strlen(args[0].as.str_val); |
1511 | 6 | while ((p = strstr(p, args[0].as.str_val)) != NULL) { cnt++; p += nlen; } |
1512 | 3 | } |
1513 | 3 | *result = value_int(cnt); |
1514 | 3 | return true; |
1515 | 3 | } |
1516 | 2 | if (strcmp(method, "is_empty") == 0 && arg_count == 0) { |
1517 | 2 | *result = value_bool(obj->as.str_val[0] == '\0'); |
1518 | 2 | return true; |
1519 | 2 | } |
1520 | 2 | } |
1521 | | |
1522 | | /* ── Map additional: remove/delete ── */ |
1523 | 243 | if (obj->type == VAL_MAP) { |
1524 | 171 | if ((strcmp(method, "remove") == 0 || strcmp(method, "delete") == 0) && arg_count == 1) { |
1525 | 1 | if (args[0].type == VAL_STR) |
1526 | 1 | lat_map_remove(obj->as.map.map, args[0].as.str_val); |
1527 | 1 | *result = value_unit(); |
1528 | 1 | return true; |
1529 | 1 | } |
1530 | 170 | if (strcmp(method, "map") == 0 && arg_count == 1) { |
1531 | 2 | LatValue *closure = &args[0]; |
1532 | 2 | LatValue mapped = value_map_new(); |
1533 | 34 | for (size_t i = 0; i < obj->as.map.map->cap; i++) { |
1534 | 32 | if (obj->as.map.map->entries[i].state != MAP_OCCUPIED) continue; |
1535 | 4 | LatValue cb_args[2]; |
1536 | 4 | cb_args[0] = value_string(obj->as.map.map->entries[i].key); |
1537 | 4 | cb_args[1] = rvm_clone((LatValue *)obj->as.map.map->entries[i].value); |
1538 | 4 | LatValue ret = regvm_call_closure(vm, closure, cb_args, 2); |
1539 | 4 | lat_map_set(mapped.as.map.map, obj->as.map.map->entries[i].key, &ret); |
1540 | 4 | value_free(&cb_args[0]); |
1541 | 4 | value_free(&cb_args[1]); |
1542 | 4 | } |
1543 | 2 | *result = mapped; |
1544 | 2 | return true; |
1545 | 2 | } |
1546 | 170 | } |
1547 | | |
1548 | | /* ── Buffer methods ── */ |
1549 | 240 | if (obj->type == VAL_BUFFER) { |
1550 | 25 | if (strcmp(method, "len") == 0 && arg_count == 0) { |
1551 | 10 | *result = value_int((int64_t)obj->as.buffer.len); |
1552 | 10 | return true; |
1553 | 10 | } |
1554 | 15 | if (strcmp(method, "capacity") == 0 && arg_count == 0) { |
1555 | 0 | *result = value_int((int64_t)obj->as.buffer.cap); |
1556 | 0 | return true; |
1557 | 0 | } |
1558 | 15 | if (strcmp(method, "push") == 0 && arg_count == 1) { |
1559 | 2 | if (args[0].type == VAL_INT) { |
1560 | 2 | if (obj->as.buffer.len >= obj->as.buffer.cap) { |
1561 | 0 | obj->as.buffer.cap = obj->as.buffer.cap ? obj->as.buffer.cap * 2 : 8; |
1562 | 0 | obj->as.buffer.data = realloc(obj->as.buffer.data, obj->as.buffer.cap); |
1563 | 0 | } |
1564 | 2 | obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)args[0].as.int_val; |
1565 | 2 | } |
1566 | 2 | *result = value_unit(); |
1567 | 2 | return true; |
1568 | 2 | } |
1569 | 13 | if (strcmp(method, "push_u16") == 0 && arg_count == 1) { |
1570 | 1 | if (args[0].type == VAL_INT) { |
1571 | 1 | uint16_t v = (uint16_t)args[0].as.int_val; |
1572 | 1 | while (obj->as.buffer.len + 2 > obj->as.buffer.cap) { |
1573 | 0 | obj->as.buffer.cap = obj->as.buffer.cap ? obj->as.buffer.cap * 2 : 8; |
1574 | 0 | obj->as.buffer.data = realloc(obj->as.buffer.data, obj->as.buffer.cap); |
1575 | 0 | } |
1576 | 1 | obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)(v & 0xFF); |
1577 | 1 | obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)((v >> 8) & 0xFF); |
1578 | 1 | } |
1579 | 1 | *result = value_unit(); |
1580 | 1 | return true; |
1581 | 1 | } |
1582 | 12 | if (strcmp(method, "push_u32") == 0 && arg_count == 1) { |
1583 | 1 | if (args[0].type == VAL_INT) { |
1584 | 1 | uint32_t v = (uint32_t)args[0].as.int_val; |
1585 | 1 | while (obj->as.buffer.len + 4 > obj->as.buffer.cap) { |
1586 | 0 | obj->as.buffer.cap = obj->as.buffer.cap ? obj->as.buffer.cap * 2 : 8; |
1587 | 0 | obj->as.buffer.data = realloc(obj->as.buffer.data, obj->as.buffer.cap); |
1588 | 0 | } |
1589 | 1 | obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)(v & 0xFF); |
1590 | 1 | obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)((v >> 8) & 0xFF); |
1591 | 1 | obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)((v >> 16) & 0xFF); |
1592 | 1 | obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)((v >> 24) & 0xFF); |
1593 | 1 | } |
1594 | 1 | *result = value_unit(); |
1595 | 1 | return true; |
1596 | 1 | } |
1597 | 11 | if (strcmp(method, "read_u8") == 0 && arg_count == 1) { |
1598 | 0 | if (args[0].type == VAL_INT) { |
1599 | 0 | int64_t idx = args[0].as.int_val; |
1600 | 0 | if (idx < 0 || (size_t)idx >= obj->as.buffer.len) { |
1601 | 0 | *result = value_nil(); |
1602 | 0 | } else { |
1603 | 0 | *result = value_int((int64_t)obj->as.buffer.data[idx]); |
1604 | 0 | } |
1605 | 0 | } else { |
1606 | 0 | *result = value_nil(); |
1607 | 0 | } |
1608 | 0 | return true; |
1609 | 0 | } |
1610 | 11 | if (strcmp(method, "write_u8") == 0 && arg_count == 2) { |
1611 | 0 | if (args[0].type == VAL_INT && args[1].type == VAL_INT) { |
1612 | 0 | int64_t idx = args[0].as.int_val; |
1613 | 0 | if (idx >= 0 && (size_t)idx < obj->as.buffer.len) |
1614 | 0 | obj->as.buffer.data[idx] = (uint8_t)args[1].as.int_val; |
1615 | 0 | } |
1616 | 0 | *result = value_unit(); |
1617 | 0 | return true; |
1618 | 0 | } |
1619 | 11 | if (strcmp(method, "read_u16") == 0 && arg_count == 1) { |
1620 | 1 | if (args[0].type == VAL_INT) { |
1621 | 1 | int64_t idx = args[0].as.int_val; |
1622 | 1 | if (idx < 0 || (size_t)idx + 2 > obj->as.buffer.len) { |
1623 | 0 | *result = value_nil(); |
1624 | 1 | } else { |
1625 | 1 | uint16_t v = (uint16_t)(obj->as.buffer.data[idx]) | |
1626 | 1 | ((uint16_t)(obj->as.buffer.data[idx + 1]) << 8); |
1627 | 1 | *result = value_int((int64_t)v); |
1628 | 1 | } |
1629 | 1 | } else { |
1630 | 0 | *result = value_nil(); |
1631 | 0 | } |
1632 | 1 | return true; |
1633 | 1 | } |
1634 | 10 | if (strcmp(method, "write_u16") == 0 && arg_count == 2) { |
1635 | 1 | if (args[0].type == VAL_INT && args[1].type == VAL_INT) { |
1636 | 1 | int64_t idx = args[0].as.int_val; |
1637 | 1 | uint16_t v = (uint16_t)args[1].as.int_val; |
1638 | 1 | if (idx >= 0 && (size_t)idx + 1 < obj->as.buffer.len) { |
1639 | 1 | obj->as.buffer.data[idx] = (uint8_t)(v & 0xFF); |
1640 | 1 | obj->as.buffer.data[idx + 1] = (uint8_t)(v >> 8); |
1641 | 1 | } |
1642 | 1 | } |
1643 | 1 | *result = value_unit(); |
1644 | 1 | return true; |
1645 | 1 | } |
1646 | 9 | if (strcmp(method, "read_u32") == 0 && arg_count == 1) { |
1647 | 1 | if (args[0].type == VAL_INT) { |
1648 | 1 | int64_t idx = args[0].as.int_val; |
1649 | 1 | if (idx < 0 || (size_t)idx + 4 > obj->as.buffer.len) { |
1650 | 0 | *result = value_nil(); |
1651 | 1 | } else { |
1652 | 1 | uint32_t v = (uint32_t)(obj->as.buffer.data[idx]) | |
1653 | 1 | ((uint32_t)(obj->as.buffer.data[idx + 1]) << 8) | |
1654 | 1 | ((uint32_t)(obj->as.buffer.data[idx + 2]) << 16) | |
1655 | 1 | ((uint32_t)(obj->as.buffer.data[idx + 3]) << 24); |
1656 | 1 | *result = value_int((int64_t)v); |
1657 | 1 | } |
1658 | 1 | } else { |
1659 | 0 | *result = value_nil(); |
1660 | 0 | } |
1661 | 1 | return true; |
1662 | 1 | } |
1663 | 8 | if (strcmp(method, "write_u32") == 0 && arg_count == 2) { |
1664 | 1 | if (args[0].type == VAL_INT && args[1].type == VAL_INT) { |
1665 | 1 | int64_t idx = args[0].as.int_val; |
1666 | 1 | uint32_t v = (uint32_t)args[1].as.int_val; |
1667 | 1 | if (idx >= 0 && (size_t)idx + 3 < obj->as.buffer.len) { |
1668 | 1 | obj->as.buffer.data[idx] = (uint8_t)(v & 0xFF); |
1669 | 1 | obj->as.buffer.data[idx + 1] = (uint8_t)((v >> 8) & 0xFF); |
1670 | 1 | obj->as.buffer.data[idx + 2] = (uint8_t)((v >> 16) & 0xFF); |
1671 | 1 | obj->as.buffer.data[idx + 3] = (uint8_t)((v >> 24) & 0xFF); |
1672 | 1 | } |
1673 | 1 | } |
1674 | 1 | *result = value_unit(); |
1675 | 1 | return true; |
1676 | 1 | } |
1677 | 7 | if (strcmp(method, "slice") == 0 && (arg_count == 1 || arg_count == 2)) { |
1678 | 1 | int64_t start = args[0].type == VAL_INT ? args[0].as.int_val : 0; |
1679 | 1 | int64_t end = arg_count == 2 && args[1].type == VAL_INT ? args[1].as.int_val : (int64_t)obj->as.buffer.len; |
1680 | 1 | if (start < 0) start = 0; |
1681 | 1 | if (end > (int64_t)obj->as.buffer.len) end = (int64_t)obj->as.buffer.len; |
1682 | 1 | if (start >= end) { *result = value_buffer(NULL, 0); return true; } |
1683 | 1 | *result = value_buffer(obj->as.buffer.data + start, (size_t)(end - start)); |
1684 | 1 | return true; |
1685 | 1 | } |
1686 | 6 | if (strcmp(method, "clear") == 0 && arg_count == 0) { |
1687 | 1 | obj->as.buffer.len = 0; |
1688 | 1 | *result = value_unit(); |
1689 | 1 | return true; |
1690 | 1 | } |
1691 | 5 | if (strcmp(method, "fill") == 0 && arg_count == 1) { |
1692 | 1 | if (args[0].type == VAL_INT) |
1693 | 1 | memset(obj->as.buffer.data, (uint8_t)args[0].as.int_val, obj->as.buffer.len); |
1694 | 1 | *result = value_unit(); |
1695 | 1 | return true; |
1696 | 1 | } |
1697 | 4 | if (strcmp(method, "resize") == 0 && arg_count == 1) { |
1698 | 1 | if (args[0].type == VAL_INT && args[0].as.int_val >= 0) { |
1699 | 1 | size_t new_len = (size_t)args[0].as.int_val; |
1700 | 1 | if (new_len > obj->as.buffer.cap) { |
1701 | 0 | obj->as.buffer.cap = new_len; |
1702 | 0 | obj->as.buffer.data = realloc(obj->as.buffer.data, new_len); |
1703 | 0 | } |
1704 | 1 | if (new_len > obj->as.buffer.len) |
1705 | 1 | memset(obj->as.buffer.data + obj->as.buffer.len, 0, new_len - obj->as.buffer.len); |
1706 | 1 | obj->as.buffer.len = new_len; |
1707 | 1 | } |
1708 | 1 | *result = value_unit(); |
1709 | 1 | return true; |
1710 | 1 | } |
1711 | 3 | if (strcmp(method, "to_string") == 0 && arg_count == 0) { |
1712 | 1 | char *s = malloc(obj->as.buffer.len + 1); |
1713 | 1 | memcpy(s, obj->as.buffer.data, obj->as.buffer.len); |
1714 | 1 | s[obj->as.buffer.len] = '\0'; |
1715 | 1 | *result = value_string_owned(s); |
1716 | 1 | return true; |
1717 | 1 | } |
1718 | 2 | if (strcmp(method, "to_array") == 0 && arg_count == 0) { |
1719 | 1 | LatValue *elems = malloc((obj->as.buffer.len > 0 ? obj->as.buffer.len : 1) * sizeof(LatValue)); |
1720 | 4 | for (size_t i = 0; i < obj->as.buffer.len; i++) |
1721 | 3 | elems[i] = value_int((int64_t)obj->as.buffer.data[i]); |
1722 | 1 | *result = value_array(elems, obj->as.buffer.len); |
1723 | 1 | free(elems); |
1724 | 1 | return true; |
1725 | 1 | } |
1726 | 1 | if (strcmp(method, "to_hex") == 0 && arg_count == 0) { |
1727 | 1 | char *hex = malloc(obj->as.buffer.len * 2 + 1); |
1728 | 4 | for (size_t i = 0; i < obj->as.buffer.len; i++) |
1729 | 3 | snprintf(hex + i * 2, 3, "%02x", obj->as.buffer.data[i]); |
1730 | 1 | hex[obj->as.buffer.len * 2] = '\0'; |
1731 | 1 | *result = value_string_owned(hex); |
1732 | 1 | return true; |
1733 | 1 | } |
1734 | 1 | } |
1735 | | |
1736 | | /* ── Ref methods ── */ |
1737 | 215 | if (obj->type == VAL_REF) { |
1738 | 15 | LatRef *ref = obj->as.ref.ref; |
1739 | 15 | if ((strcmp(method, "get") == 0 || strcmp(method, "deref") == 0) && arg_count == 0) { |
1740 | 4 | *result = value_deep_clone(&ref->value); |
1741 | 4 | return true; |
1742 | 4 | } |
1743 | 11 | if (strcmp(method, "set") == 0 && arg_count == 1 && arg_count == 1) { |
1744 | 2 | if (obj->phase == VTAG_CRYSTAL) { |
1745 | 0 | *result = value_unit(); |
1746 | 0 | return true; |
1747 | 0 | } |
1748 | 2 | value_free(&ref->value); |
1749 | 2 | ref->value = rvm_clone(&args[0]); |
1750 | 2 | *result = value_unit(); |
1751 | 2 | return true; |
1752 | 2 | } |
1753 | 9 | if (strcmp(method, "inner_type") == 0 && arg_count == 0) { |
1754 | 2 | *result = value_string(value_type_name(&ref->value)); |
1755 | 2 | return true; |
1756 | 2 | } |
1757 | | /* Proxy: delegate to inner value's methods if applicable */ |
1758 | 7 | if (ref->value.type == VAL_MAP) { |
1759 | 3 | if (strcmp(method, "get") == 0 && arg_count == 1 && args[0].type == VAL_STR) { |
1760 | 0 | LatValue *val = lat_map_get(ref->value.as.map.map, args[0].as.str_val); |
1761 | 0 | *result = val ? value_deep_clone(val) : value_nil(); |
1762 | 0 | return true; |
1763 | 0 | } |
1764 | 3 | if (strcmp(method, "set") == 0 && arg_count == 2 && args[0].type == VAL_STR) { |
1765 | 0 | if (obj->phase != VTAG_CRYSTAL) { |
1766 | 0 | LatValue cloned = rvm_clone(&args[1]); |
1767 | 0 | lat_map_set(ref->value.as.map.map, args[0].as.str_val, &cloned); |
1768 | 0 | } |
1769 | 0 | *result = value_unit(); |
1770 | 0 | return true; |
1771 | 0 | } |
1772 | 3 | if ((strcmp(method, "has") == 0 || strcmp(method, "contains") == 0) && arg_count == 1 && args[0].type == VAL_STR) { |
1773 | 2 | *result = value_bool(lat_map_get(ref->value.as.map.map, args[0].as.str_val) != NULL); |
1774 | 2 | return true; |
1775 | 2 | } |
1776 | 1 | if (strcmp(method, "keys") == 0 && arg_count == 0) { |
1777 | 0 | size_t cap = ref->value.as.map.map->cap; |
1778 | 0 | LatValue *keys = malloc(cap * sizeof(LatValue)); |
1779 | 0 | size_t cnt = 0; |
1780 | 0 | for (size_t i = 0; i < cap; i++) { |
1781 | 0 | if (ref->value.as.map.map->entries[i].state == MAP_OCCUPIED) |
1782 | 0 | keys[cnt++] = value_string(ref->value.as.map.map->entries[i].key); |
1783 | 0 | } |
1784 | 0 | *result = value_array(keys, cnt); |
1785 | 0 | free(keys); |
1786 | 0 | return true; |
1787 | 0 | } |
1788 | 1 | if (strcmp(method, "values") == 0 && arg_count == 0) { |
1789 | 0 | size_t cap = ref->value.as.map.map->cap; |
1790 | 0 | LatValue *vals = malloc(cap * sizeof(LatValue)); |
1791 | 0 | size_t cnt = 0; |
1792 | 0 | for (size_t i = 0; i < cap; i++) { |
1793 | 0 | if (ref->value.as.map.map->entries[i].state == MAP_OCCUPIED) |
1794 | 0 | vals[cnt++] = value_deep_clone((LatValue *)ref->value.as.map.map->entries[i].value); |
1795 | 0 | } |
1796 | 0 | *result = value_array(vals, cnt); |
1797 | 0 | free(vals); |
1798 | 0 | return true; |
1799 | 0 | } |
1800 | 1 | if (strcmp(method, "entries") == 0 && arg_count == 0) { |
1801 | 0 | size_t cap = ref->value.as.map.map->cap; |
1802 | 0 | LatValue *entries = malloc(cap * sizeof(LatValue)); |
1803 | 0 | size_t cnt = 0; |
1804 | 0 | for (size_t i = 0; i < cap; i++) { |
1805 | 0 | if (ref->value.as.map.map->entries[i].state != MAP_OCCUPIED) continue; |
1806 | 0 | LatValue pair[2]; |
1807 | 0 | pair[0] = value_string(ref->value.as.map.map->entries[i].key); |
1808 | 0 | pair[1] = value_deep_clone((LatValue *)ref->value.as.map.map->entries[i].value); |
1809 | 0 | entries[cnt++] = value_array(pair, 2); |
1810 | 0 | } |
1811 | 0 | *result = value_array(entries, cnt); |
1812 | 0 | free(entries); |
1813 | 0 | return true; |
1814 | 0 | } |
1815 | 1 | if (strcmp(method, "len") == 0 && arg_count == 0) { |
1816 | 1 | *result = value_int((int64_t)lat_map_len(ref->value.as.map.map)); |
1817 | 1 | return true; |
1818 | 1 | } |
1819 | 0 | if (strcmp(method, "merge") == 0 && arg_count == 1 && args[0].type == VAL_MAP) { |
1820 | 0 | if (obj->phase != VTAG_CRYSTAL) { |
1821 | 0 | for (size_t i = 0; i < args[0].as.map.map->cap; i++) { |
1822 | 0 | if (args[0].as.map.map->entries[i].state != MAP_OCCUPIED) continue; |
1823 | 0 | LatValue v = rvm_clone((LatValue *)args[0].as.map.map->entries[i].value); |
1824 | 0 | lat_map_set(ref->value.as.map.map, args[0].as.map.map->entries[i].key, &v); |
1825 | 0 | } |
1826 | 0 | } |
1827 | 0 | *result = value_unit(); |
1828 | 0 | return true; |
1829 | 0 | } |
1830 | 0 | } |
1831 | 4 | if (ref->value.type == VAL_ARRAY) { |
1832 | 4 | if (strcmp(method, "push") == 0 && arg_count == 1) { |
1833 | 1 | LatValue val = rvm_clone(&args[0]); |
1834 | 1 | if (ref->value.as.array.len >= ref->value.as.array.cap) { |
1835 | 0 | ref->value.as.array.cap = ref->value.as.array.cap ? ref->value.as.array.cap * 2 : 4; |
1836 | 0 | ref->value.as.array.elems = realloc(ref->value.as.array.elems, |
1837 | 0 | ref->value.as.array.cap * sizeof(LatValue)); |
1838 | 0 | } |
1839 | 1 | ref->value.as.array.elems[ref->value.as.array.len++] = val; |
1840 | 1 | *result = value_unit(); |
1841 | 1 | return true; |
1842 | 1 | } |
1843 | 3 | if (strcmp(method, "pop") == 0 && arg_count == 0) { |
1844 | 1 | if (ref->value.as.array.len == 0) { *result = value_nil(); } |
1845 | 1 | else { *result = ref->value.as.array.elems[--ref->value.as.array.len]; } |
1846 | 1 | return true; |
1847 | 1 | } |
1848 | 2 | if (strcmp(method, "len") == 0 && arg_count == 0) { |
1849 | 2 | *result = value_int((int64_t)ref->value.as.array.len); |
1850 | 2 | return true; |
1851 | 2 | } |
1852 | 0 | if (strcmp(method, "contains") == 0 && arg_count == 1) { |
1853 | 0 | bool found = false; |
1854 | 0 | for (size_t i = 0; i < ref->value.as.array.len; i++) { |
1855 | 0 | if (value_eq(&ref->value.as.array.elems[i], &args[0])) { found = true; break; } |
1856 | 0 | } |
1857 | 0 | *result = value_bool(found); |
1858 | 0 | return true; |
1859 | 0 | } |
1860 | 0 | } |
1861 | 4 | } |
1862 | | |
1863 | | /* ── Channel methods ── */ |
1864 | 200 | if (obj->type == VAL_CHANNEL) { |
1865 | 21 | if (strcmp(method, "send") == 0 && arg_count == 1) { |
1866 | 10 | if (!value_is_crystal(&args[0]) && args[0].phase != VTAG_UNPHASED) { |
1867 | 1 | vm->error = strdup("channel send requires crystal or unphased values"); |
1868 | 1 | *result = value_unit(); |
1869 | 1 | return true; |
1870 | 1 | } |
1871 | 9 | LatValue val = rvm_clone(&args[0]); |
1872 | 9 | channel_send(obj->as.channel.ch, val); |
1873 | 9 | *result = value_unit(); |
1874 | 9 | return true; |
1875 | 10 | } |
1876 | 11 | if (strcmp(method, "recv") == 0 && arg_count == 0) { |
1877 | 8 | bool ok = false; |
1878 | 8 | *result = channel_recv(obj->as.channel.ch, &ok); |
1879 | 8 | if (!ok) *result = value_unit(); |
1880 | 8 | return true; |
1881 | 8 | } |
1882 | 3 | if (strcmp(method, "close") == 0 && arg_count == 0) { |
1883 | 3 | channel_close(obj->as.channel.ch); |
1884 | 3 | *result = value_unit(); |
1885 | 3 | return true; |
1886 | 3 | } |
1887 | 3 | } |
1888 | | |
1889 | 179 | return false; |
1890 | 200 | } |
1891 | | |
1892 | | /* Phase system functions (regvm_fire_reactions, regvm_freeze_cascade, |
1893 | | * regvm_validate_seeds, regvm_sync_env_to_register) have been moved to |
1894 | | * runtime.c as rt_* functions. */ |
1895 | | |
1896 | | /* ── VM Dispatch Loop ── */ |
1897 | | |
1898 | | /* Native function type (same as stack VM) */ |
1899 | | typedef LatValue (*VMNativeFn)(LatValue *args, int arg_count); |
1900 | | |
1901 | | /* Call a closure from within a builtin handler (map, filter, etc.). */ |
1902 | 287 | static LatValue regvm_call_closure(RegVM *vm, LatValue *closure, LatValue *args, int argc) { |
1903 | 287 | if (closure->type != VAL_CLOSURE) return value_nil(); |
1904 | | |
1905 | | /* Check for native C function */ |
1906 | 287 | if (closure->as.closure.default_values == VM_NATIVE_MARKER) { |
1907 | 0 | VMNativeFn native = (VMNativeFn)closure->as.closure.native_fn; |
1908 | 0 | LatValue ret = native(args, argc); |
1909 | | /* Check runtime for native errors — propagate to regvm */ |
1910 | 0 | if (vm->rt->error) { |
1911 | 0 | vm->error = vm->rt->error; |
1912 | 0 | vm->rt->error = NULL; |
1913 | 0 | value_free(&ret); |
1914 | 0 | return value_nil(); |
1915 | 0 | } |
1916 | 0 | return ret; |
1917 | 0 | } |
1918 | | |
1919 | | /* Extension native function */ |
1920 | 287 | if (closure->as.closure.default_values == VM_EXT_MARKER) { |
1921 | 0 | LatValue ret = ext_call_native(closure->as.closure.native_fn, args, (size_t)argc); |
1922 | 0 | if (ret.type == VAL_STR && ret.as.str_val && |
1923 | 0 | strncmp(ret.as.str_val, "EVAL_ERROR:", 11) == 0) { |
1924 | 0 | vm->error = strdup(ret.as.str_val + 11); |
1925 | 0 | value_free(&ret); |
1926 | 0 | return value_nil(); |
1927 | 0 | } |
1928 | 0 | return ret; |
1929 | 0 | } |
1930 | | |
1931 | 287 | RegChunk *fn_chunk = (RegChunk *)closure->as.closure.native_fn; |
1932 | 287 | if (!fn_chunk) return value_nil(); |
1933 | | |
1934 | | /* Guard: detect stack-VM closures that can't run in regvm */ |
1935 | 287 | if (fn_chunk->magic != REGCHUNK_MAGIC) return value_nil(); |
1936 | | |
1937 | 287 | if (vm->frame_count >= REGVM_FRAMES_MAX) return value_nil(); |
1938 | | |
1939 | 287 | size_t new_base = vm->reg_stack_top; |
1940 | 287 | if (new_base + REGVM_REG_MAX > REGVM_REG_MAX * REGVM_FRAMES_MAX) |
1941 | 0 | return value_nil(); |
1942 | | |
1943 | 287 | LatValue *new_regs = &vm->reg_stack[new_base]; |
1944 | 287 | vm->reg_stack_top += REGVM_REG_MAX; |
1945 | 73.7k | for (int i = 0; i < REGVM_REG_MAX; i++) |
1946 | 73.4k | new_regs[i] = value_nil(); |
1947 | | |
1948 | 287 | new_regs[0] = value_unit(); |
1949 | 616 | for (int i = 0; i < argc; i++) { |
1950 | 329 | value_free(&new_regs[1 + i]); |
1951 | 329 | new_regs[1 + i] = rvm_clone(&args[i]); |
1952 | 329 | } |
1953 | | |
1954 | 287 | ObjUpvalue **upvals = (ObjUpvalue **)closure->as.closure.captured_env; |
1955 | 287 | size_t uv_count = closure->region_id != (size_t)-1 ? closure->region_id : 0; |
1956 | | |
1957 | 287 | int saved_base = vm->frame_count; |
1958 | 287 | RegCallFrame *new_frame = &vm->frames[vm->frame_count++]; |
1959 | 287 | new_frame->chunk = fn_chunk; |
1960 | 287 | new_frame->ip = fn_chunk->code; |
1961 | 287 | new_frame->regs = new_regs; |
1962 | 287 | new_frame->reg_count = REGVM_REG_MAX; |
1963 | 287 | new_frame->upvalues = upvals; |
1964 | 287 | new_frame->upvalue_count = uv_count; |
1965 | 287 | new_frame->caller_result_reg = 0; |
1966 | | |
1967 | 287 | LatValue ret; |
1968 | 287 | RegVMResult res = regvm_dispatch(vm, saved_base, &ret); |
1969 | 287 | if (res != REGVM_OK) { |
1970 | | /* Unwind any frames left by the failed dispatch back to saved_base */ |
1971 | 2 | while (vm->frame_count > saved_base) { |
1972 | 1 | RegCallFrame *uf = &vm->frames[vm->frame_count - 1]; |
1973 | 257 | for (int i = 0; i < REGVM_REG_MAX; i++) |
1974 | 256 | value_free_inline(&uf->regs[i]); |
1975 | 1 | vm->frame_count--; |
1976 | 1 | vm->reg_stack_top -= REGVM_REG_MAX; |
1977 | 1 | } |
1978 | | /* Propagate vm->error to rt->error so runtime-level callers |
1979 | | * (e.g. rt_fire_reactions) can see and wrap the error */ |
1980 | 1 | if (vm->error && !vm->rt->error) { |
1981 | 1 | vm->rt->error = vm->error; |
1982 | 1 | vm->error = NULL; |
1983 | 1 | } |
1984 | 1 | return value_nil(); |
1985 | 1 | } |
1986 | 286 | return ret; |
1987 | 287 | } |
1988 | | |
1989 | 1.25k | static RegVMResult regvm_dispatch(RegVM *vm, int base_frame, LatValue *result) { |
1990 | 1.25k | RegCallFrame *frame = &vm->frames[vm->frame_count - 1]; |
1991 | | |
1992 | 1.25k | LatValue *R = frame->regs; /* Register base pointer */ |
1993 | | |
1994 | | /* Route runtime errors through exception handlers when possible */ |
1995 | 1.25k | #define RVM_ERROR(...) do { \ |
1996 | 104 | RegVMResult _err = rvm_handle_error(vm, &frame, &R, __VA_ARGS__); \ |
1997 | 104 | if (_err != REGVM_OK) return _err; \ |
1998 | 104 | DISPATCH(); \ |
1999 | 29 | } while(0) |
2000 | | |
2001 | 52.6k | #define READ_INSTR() (*frame->ip++) |
2002 | 1.25k | #define REGS R |
2003 | | |
2004 | 1.25k | #ifdef VM_USE_COMPUTED_GOTO |
2005 | | /* Computed goto dispatch table */ |
2006 | 1.25k | static void *dispatch_table[ROP_COUNT] = { |
2007 | 1.25k | [ROP_MOVE] = &&L_MOVE, |
2008 | 1.25k | [ROP_LOADK] = &&L_LOADK, |
2009 | 1.25k | [ROP_LOADI] = &&L_LOADI, |
2010 | 1.25k | [ROP_LOADNIL] = &&L_LOADNIL, |
2011 | 1.25k | [ROP_LOADTRUE] = &&L_LOADTRUE, |
2012 | 1.25k | [ROP_LOADFALSE] = &&L_LOADFALSE, |
2013 | 1.25k | [ROP_LOADUNIT] = &&L_LOADUNIT, |
2014 | 1.25k | [ROP_ADD] = &&L_ADD, |
2015 | 1.25k | [ROP_SUB] = &&L_SUB, |
2016 | 1.25k | [ROP_MUL] = &&L_MUL, |
2017 | 1.25k | [ROP_DIV] = &&L_DIV, |
2018 | 1.25k | [ROP_MOD] = &&L_MOD, |
2019 | 1.25k | [ROP_NEG] = &&L_NEG, |
2020 | 1.25k | [ROP_ADDI] = &&L_ADDI, |
2021 | 1.25k | [ROP_CONCAT] = &&L_CONCAT, |
2022 | 1.25k | [ROP_EQ] = &&L_EQ, |
2023 | 1.25k | [ROP_NEQ] = &&L_NEQ, |
2024 | 1.25k | [ROP_LT] = &&L_LT, |
2025 | 1.25k | [ROP_LTEQ] = &&L_LTEQ, |
2026 | 1.25k | [ROP_GT] = &&L_GT, |
2027 | 1.25k | [ROP_GTEQ] = &&L_GTEQ, |
2028 | 1.25k | [ROP_NOT] = &&L_NOT, |
2029 | 1.25k | [ROP_JMP] = &&L_JMP, |
2030 | 1.25k | [ROP_JMPFALSE] = &&L_JMPFALSE, |
2031 | 1.25k | [ROP_JMPTRUE] = &&L_JMPTRUE, |
2032 | 1.25k | [ROP_GETGLOBAL] = &&L_GETGLOBAL, |
2033 | 1.25k | [ROP_SETGLOBAL] = &&L_SETGLOBAL, |
2034 | 1.25k | [ROP_DEFINEGLOBAL] = &&L_DEFINEGLOBAL, |
2035 | 1.25k | [ROP_GETFIELD] = &&L_GETFIELD, |
2036 | 1.25k | [ROP_SETFIELD] = &&L_SETFIELD, |
2037 | 1.25k | [ROP_GETINDEX] = &&L_GETINDEX, |
2038 | 1.25k | [ROP_SETINDEX] = &&L_SETINDEX, |
2039 | 1.25k | [ROP_GETUPVALUE] = &&L_GETUPVALUE, |
2040 | 1.25k | [ROP_SETUPVALUE] = &&L_SETUPVALUE, |
2041 | 1.25k | [ROP_CLOSEUPVALUE] = &&L_CLOSEUPVALUE, |
2042 | 1.25k | [ROP_CALL] = &&L_CALL, |
2043 | 1.25k | [ROP_RETURN] = &&L_RETURN, |
2044 | 1.25k | [ROP_CLOSURE] = &&L_CLOSURE, |
2045 | 1.25k | [ROP_NEWARRAY] = &&L_NEWARRAY, |
2046 | 1.25k | [ROP_NEWSTRUCT] = &&L_NEWSTRUCT, |
2047 | 1.25k | [ROP_BUILDRANGE] = &&L_BUILDRANGE, |
2048 | 1.25k | [ROP_LEN] = &&L_LEN, |
2049 | 1.25k | [ROP_PRINT] = &&L_PRINT, |
2050 | 1.25k | [ROP_INVOKE] = &&L_INVOKE, |
2051 | 1.25k | [ROP_FREEZE] = &&L_FREEZE, |
2052 | 1.25k | [ROP_THAW] = &&L_THAW, |
2053 | 1.25k | [ROP_CLONE] = &&L_CLONE, |
2054 | 1.25k | [ROP_ITERINIT] = &&L_ITERINIT, |
2055 | 1.25k | [ROP_ITERNEXT] = &&L_ITERNEXT, |
2056 | 1.25k | [ROP_MARKFLUID] = &&L_MARKFLUID, |
2057 | | /* Bitwise */ |
2058 | 1.25k | [ROP_BIT_AND] = &&L_BIT_AND, |
2059 | 1.25k | [ROP_BIT_OR] = &&L_BIT_OR, |
2060 | 1.25k | [ROP_BIT_XOR] = &&L_BIT_XOR, |
2061 | 1.25k | [ROP_BIT_NOT] = &&L_BIT_NOT, |
2062 | 1.25k | [ROP_LSHIFT] = &&L_LSHIFT, |
2063 | 1.25k | [ROP_RSHIFT] = &&L_RSHIFT, |
2064 | | /* Tuple */ |
2065 | 1.25k | [ROP_NEWTUPLE] = &&L_NEWTUPLE, |
2066 | | /* Spread/Flatten */ |
2067 | 1.25k | [ROP_ARRAY_FLATTEN] = &&L_ARRAY_FLATTEN, |
2068 | | /* Enum */ |
2069 | 1.25k | [ROP_NEWENUM] = &&L_NEWENUM, |
2070 | | /* Optional chaining */ |
2071 | 1.25k | [ROP_JMPNOTNIL] = &&L_JMPNOTNIL, |
2072 | | /* Exception handling */ |
2073 | 1.25k | [ROP_PUSH_HANDLER] = &&L_PUSH_HANDLER, |
2074 | 1.25k | [ROP_POP_HANDLER] = &&L_POP_HANDLER, |
2075 | 1.25k | [ROP_THROW] = &&L_THROW, |
2076 | 1.25k | [ROP_TRY_UNWRAP] = &&L_TRY_UNWRAP, |
2077 | | /* Defer */ |
2078 | 1.25k | [ROP_DEFER_PUSH] = &&L_DEFER_PUSH, |
2079 | 1.25k | [ROP_DEFER_RUN] = &&L_DEFER_RUN, |
2080 | | /* Variadic */ |
2081 | 1.25k | [ROP_COLLECT_VARARGS] = &&L_COLLECT_VARARGS, |
2082 | | /* Advanced phase */ |
2083 | 1.25k | [ROP_FREEZE_VAR] = &&L_FREEZE_VAR, |
2084 | 1.25k | [ROP_THAW_VAR] = &&L_THAW_VAR, |
2085 | 1.25k | [ROP_SUBLIMATE_VAR] = &&L_SUBLIMATE_VAR, |
2086 | 1.25k | [ROP_REACT] = &&L_REACT, |
2087 | 1.25k | [ROP_UNREACT] = &&L_UNREACT, |
2088 | 1.25k | [ROP_BOND] = &&L_BOND, |
2089 | 1.25k | [ROP_UNBOND] = &&L_UNBOND, |
2090 | 1.25k | [ROP_SEED] = &&L_SEED, |
2091 | 1.25k | [ROP_UNSEED] = &&L_UNSEED, |
2092 | | /* Module/Import */ |
2093 | 1.25k | [ROP_IMPORT] = &&L_IMPORT, |
2094 | | /* Concurrency */ |
2095 | 1.25k | [ROP_SCOPE] = &&L_SCOPE, |
2096 | 1.25k | [ROP_SELECT] = &&L_SELECT, |
2097 | | /* Ephemeral arena */ |
2098 | 1.25k | [ROP_RESET_EPHEMERAL] = &&L_RESET_EPHEMERAL, |
2099 | | /* Optimization */ |
2100 | 1.25k | [ROP_ADD_INT] = &&L_ADD_INT, |
2101 | 1.25k | [ROP_SUB_INT] = &&L_SUB_INT, |
2102 | 1.25k | [ROP_MUL_INT] = &&L_MUL_INT, |
2103 | 1.25k | [ROP_LT_INT] = &&L_LT_INT, |
2104 | 1.25k | [ROP_LTEQ_INT] = &&L_LTEQ_INT, |
2105 | 1.25k | [ROP_INC_REG] = &&L_INC_REG, |
2106 | 1.25k | [ROP_DEC_REG] = &&L_DEC_REG, |
2107 | 1.25k | [ROP_SETINDEX_LOCAL] = &&L_SETINDEX_LOCAL, |
2108 | 1.25k | [ROP_INVOKE_GLOBAL] = &&L_INVOKE_GLOBAL, |
2109 | | /* Phase query */ |
2110 | 1.25k | [ROP_IS_CRYSTAL] = &&L_IS_CRYSTAL, |
2111 | | /* Type checking */ |
2112 | 1.25k | [ROP_CHECK_TYPE] = &&L_CHECK_TYPE, |
2113 | 1.25k | [ROP_FREEZE_FIELD] = &&L_FREEZE_FIELD, |
2114 | 1.25k | [ROP_THAW_FIELD] = &&L_THAW_FIELD, |
2115 | | /* Require */ |
2116 | 1.25k | [ROP_REQUIRE] = &&L_REQUIRE, |
2117 | | /* Misc */ |
2118 | 1.25k | [ROP_HALT] = &&L_HALT, |
2119 | 1.25k | }; |
2120 | | |
2121 | 1.25k | #define DISPATCH() do { \ |
2122 | 1.25k | RegInstr _i = READ_INSTR(); \ |
2123 | 1.25k | goto *dispatch_table[REG_GET_OP(_i)]; \ |
2124 | 1.25k | } while(0) |
2125 | | |
2126 | | /* We need the instruction available after goto. Use a local. */ |
2127 | 1.25k | #undef DISPATCH |
2128 | 52.3k | #define DISPATCH() do { \ |
2129 | 52.3k | instr = READ_INSTR(); \ |
2130 | 52.3k | goto *dispatch_table[REG_GET_OP(instr)]; \ |
2131 | 52.3k | } while(0) |
2132 | | |
2133 | 1.25k | RegInstr instr; |
2134 | 1.25k | DISPATCH(); |
2135 | | |
2136 | 50.5k | #define CASE(label) L_##label: |
2137 | | |
2138 | | #else |
2139 | | /* Switch-based dispatch */ |
2140 | | for (;;) { |
2141 | | RegInstr instr = READ_INSTR(); |
2142 | | switch (REG_GET_OP(instr)) { |
2143 | | |
2144 | | #define CASE(label) case ROP_##label: |
2145 | | #define DISPATCH() continue |
2146 | | |
2147 | | #endif |
2148 | | |
2149 | 7.70k | CASE(MOVE) { |
2150 | 7.70k | uint8_t a = REG_GET_A(instr); |
2151 | 7.70k | uint8_t b = REG_GET_B(instr); |
2152 | 7.70k | reg_set(&R[a], rvm_clone(&R[b])); |
2153 | | /* Record history for tracked variables */ |
2154 | 7.70k | { |
2155 | 7.70k | if (vm->rt->tracking_active && |
2156 | 7.70k | frame->chunk->local_names && a < frame->chunk->local_name_cap && |
2157 | 7.70k | frame->chunk->local_names[a] && frame->chunk->local_names[a][0]) { |
2158 | 23 | rt_record_history(vm->rt, frame->chunk->local_names[a], &R[a]); |
2159 | 23 | } |
2160 | 7.70k | } |
2161 | 7.70k | DISPATCH(); |
2162 | 7.70k | } |
2163 | | |
2164 | 7.70k | CASE(LOADK) { |
2165 | 3.63k | uint8_t a = REG_GET_A(instr); |
2166 | 3.63k | uint16_t bx = REG_GET_Bx(instr); |
2167 | 3.63k | reg_set(&R[a], rvm_clone(&frame->chunk->constants[bx])); |
2168 | | /* Record history for tracked variables */ |
2169 | 3.63k | { |
2170 | 3.63k | if (vm->rt->tracking_active && |
2171 | 3.63k | frame->chunk->local_names && a < frame->chunk->local_name_cap && |
2172 | 3.63k | frame->chunk->local_names[a] && frame->chunk->local_names[a][0]) { |
2173 | 1 | rt_record_history(vm->rt, frame->chunk->local_names[a], &R[a]); |
2174 | 1 | } |
2175 | 3.63k | } |
2176 | 3.63k | DISPATCH(); |
2177 | 3.63k | } |
2178 | | |
2179 | 3.63k | CASE(LOADI) { |
2180 | 2.27k | uint8_t a = REG_GET_A(instr); |
2181 | 2.27k | int16_t sbx = REG_GET_sBx(instr); |
2182 | 2.27k | reg_set(&R[a], value_int((int64_t)sbx)); |
2183 | | /* Record history for tracked variables */ |
2184 | 2.27k | { |
2185 | 2.27k | if (vm->rt->tracking_active && |
2186 | 2.27k | frame->chunk->local_names && a < frame->chunk->local_name_cap && |
2187 | 2.27k | frame->chunk->local_names[a] && frame->chunk->local_names[a][0]) { |
2188 | 12 | rt_record_history(vm->rt, frame->chunk->local_names[a], &R[a]); |
2189 | 12 | } |
2190 | 2.27k | } |
2191 | 2.27k | DISPATCH(); |
2192 | 2.27k | } |
2193 | | |
2194 | 2.27k | CASE(LOADNIL) { |
2195 | 96 | uint8_t a = REG_GET_A(instr); |
2196 | 96 | reg_set(&R[a], value_nil()); |
2197 | 96 | DISPATCH(); |
2198 | 96 | } |
2199 | | |
2200 | 98 | CASE(LOADTRUE) { |
2201 | 98 | uint8_t a = REG_GET_A(instr); |
2202 | 98 | reg_set(&R[a], value_bool(true)); |
2203 | 98 | { |
2204 | 98 | if (vm->rt->tracking_active && |
2205 | 98 | frame->chunk->local_names && a < frame->chunk->local_name_cap && |
2206 | 98 | frame->chunk->local_names[a] && frame->chunk->local_names[a][0]) { |
2207 | 1 | rt_record_history(vm->rt, frame->chunk->local_names[a], &R[a]); |
2208 | 1 | } |
2209 | 98 | } |
2210 | 98 | DISPATCH(); |
2211 | 98 | } |
2212 | | |
2213 | 245 | CASE(LOADFALSE) { |
2214 | 245 | uint8_t a = REG_GET_A(instr); |
2215 | 245 | reg_set(&R[a], value_bool(false)); |
2216 | 245 | { |
2217 | 245 | if (vm->rt->tracking_active && |
2218 | 245 | frame->chunk->local_names && a < frame->chunk->local_name_cap && |
2219 | 245 | frame->chunk->local_names[a] && frame->chunk->local_names[a][0]) { |
2220 | 0 | rt_record_history(vm->rt, frame->chunk->local_names[a], &R[a]); |
2221 | 0 | } |
2222 | 245 | } |
2223 | 245 | DISPATCH(); |
2224 | 245 | } |
2225 | | |
2226 | 2.72k | CASE(LOADUNIT) { |
2227 | 2.72k | uint8_t a = REG_GET_A(instr); |
2228 | 2.72k | reg_set(&R[a], value_unit()); |
2229 | 2.72k | DISPATCH(); |
2230 | 2.72k | } |
2231 | | |
2232 | 2.72k | CASE(ADD) { |
2233 | 705 | uint8_t a = REG_GET_A(instr); |
2234 | 705 | uint8_t b = REG_GET_B(instr); |
2235 | 705 | uint8_t c = REG_GET_C(instr); |
2236 | 705 | if (R[b].type == VAL_INT && R[c].type == VAL_INT) { |
2237 | 429 | reg_set(&R[a], value_int(R[b].as.int_val + R[c].as.int_val)); |
2238 | 429 | } else if (R[b].type == VAL_FLOAT || R[c].type == VAL_FLOAT) { |
2239 | 2 | double lv = R[b].type == VAL_FLOAT ? R[b].as.float_val : (double)R[b].as.int_val; |
2240 | 2 | double rv = R[c].type == VAL_FLOAT ? R[c].as.float_val : (double)R[c].as.int_val; |
2241 | 2 | reg_set(&R[a], value_float(lv + rv)); |
2242 | 274 | } else if (R[b].type == VAL_STR && R[c].type == VAL_STR) { |
2243 | 274 | size_t lb = strlen(R[b].as.str_val); |
2244 | 274 | size_t lc = strlen(R[c].as.str_val); |
2245 | 274 | char *buf = bump_alloc(vm->ephemeral, lb + lc + 1); |
2246 | 274 | memcpy(buf, R[b].as.str_val, lb); |
2247 | 274 | memcpy(buf + lb, R[c].as.str_val, lc); |
2248 | 274 | buf[lb + lc] = '\0'; |
2249 | 274 | LatValue v = { .type = VAL_STR, .phase = VTAG_UNPHASED, .region_id = REGION_EPHEMERAL }; |
2250 | 274 | v.as.str_val = buf; |
2251 | 274 | reg_set(&R[a], v); |
2252 | 274 | } else { |
2253 | 0 | RVM_ERROR("cannot add %s and %s", |
2254 | 0 | value_type_name(&R[b]), value_type_name(&R[c])); |
2255 | 0 | } |
2256 | 705 | DISPATCH(); |
2257 | 705 | } |
2258 | | |
2259 | 44 | CASE(SUB) { |
2260 | 44 | uint8_t a = REG_GET_A(instr); |
2261 | 44 | uint8_t b = REG_GET_B(instr); |
2262 | 44 | uint8_t c = REG_GET_C(instr); |
2263 | 44 | if (R[b].type == VAL_INT && R[c].type == VAL_INT) { |
2264 | 43 | reg_set(&R[a], value_int(R[b].as.int_val - R[c].as.int_val)); |
2265 | 43 | } else if (R[b].type == VAL_FLOAT || R[c].type == VAL_FLOAT) { |
2266 | 1 | double lv = R[b].type == VAL_FLOAT ? R[b].as.float_val : (double)R[b].as.int_val; |
2267 | 1 | double rv = R[c].type == VAL_FLOAT ? R[c].as.float_val : (double)R[c].as.int_val; |
2268 | 1 | reg_set(&R[a], value_float(lv - rv)); |
2269 | 1 | } else { |
2270 | 0 | RVM_ERROR("cannot subtract %s from %s", |
2271 | 0 | value_type_name(&R[c]), value_type_name(&R[b])); |
2272 | 0 | } |
2273 | 44 | DISPATCH(); |
2274 | 44 | } |
2275 | | |
2276 | 213 | CASE(MUL) { |
2277 | 213 | uint8_t a = REG_GET_A(instr); |
2278 | 213 | uint8_t b = REG_GET_B(instr); |
2279 | 213 | uint8_t c = REG_GET_C(instr); |
2280 | 213 | if (R[b].type == VAL_INT && R[c].type == VAL_INT) { |
2281 | 211 | reg_set(&R[a], value_int(R[b].as.int_val * R[c].as.int_val)); |
2282 | 211 | } else if (R[b].type == VAL_FLOAT || R[c].type == VAL_FLOAT) { |
2283 | 2 | double lv = R[b].type == VAL_FLOAT ? R[b].as.float_val : (double)R[b].as.int_val; |
2284 | 2 | double rv = R[c].type == VAL_FLOAT ? R[c].as.float_val : (double)R[c].as.int_val; |
2285 | 2 | reg_set(&R[a], value_float(lv * rv)); |
2286 | 2 | } else { |
2287 | 0 | RVM_ERROR("cannot multiply %s and %s", |
2288 | 0 | value_type_name(&R[b]), value_type_name(&R[c])); |
2289 | 0 | } |
2290 | 213 | DISPATCH(); |
2291 | 213 | } |
2292 | | |
2293 | 12 | CASE(DIV) { |
2294 | 12 | uint8_t a = REG_GET_A(instr); |
2295 | 12 | uint8_t b = REG_GET_B(instr); |
2296 | 12 | uint8_t c = REG_GET_C(instr); |
2297 | 12 | if (R[b].type == VAL_INT && R[c].type == VAL_INT) { |
2298 | 10 | if (R[c].as.int_val == 0) |
2299 | 8 | RVM_ERROR("division by zero"); |
2300 | 9 | reg_set(&R[a], value_int(R[b].as.int_val / R[c].as.int_val)); |
2301 | 9 | } else if (R[b].type == VAL_FLOAT || R[c].type == VAL_FLOAT) { |
2302 | 2 | double rv = R[c].type == VAL_FLOAT ? R[c].as.float_val : (double)R[c].as.int_val; |
2303 | 2 | double lv = R[b].type == VAL_FLOAT ? R[b].as.float_val : (double)R[b].as.int_val; |
2304 | 2 | reg_set(&R[a], value_float(lv / rv)); /* float div by zero → Inf/NaN */ |
2305 | 2 | } else { |
2306 | 0 | RVM_ERROR("cannot divide %s by %s", |
2307 | 0 | value_type_name(&R[b]), value_type_name(&R[c])); |
2308 | 0 | } |
2309 | 11 | DISPATCH(); |
2310 | 11 | } |
2311 | | |
2312 | 106 | CASE(MOD) { |
2313 | 106 | uint8_t a = REG_GET_A(instr); |
2314 | 106 | uint8_t b = REG_GET_B(instr); |
2315 | 106 | uint8_t c = REG_GET_C(instr); |
2316 | 106 | if (R[b].type == VAL_INT && R[c].type == VAL_INT) { |
2317 | 106 | if (R[c].as.int_val == 0) |
2318 | 0 | RVM_ERROR("modulo by zero"); |
2319 | 106 | reg_set(&R[a], value_int(R[b].as.int_val % R[c].as.int_val)); |
2320 | 106 | } else { |
2321 | 0 | RVM_ERROR("cannot modulo %s by %s", |
2322 | 0 | value_type_name(&R[b]), value_type_name(&R[c])); |
2323 | 0 | } |
2324 | 106 | DISPATCH(); |
2325 | 106 | } |
2326 | | |
2327 | 16 | CASE(NEG) { |
2328 | 16 | uint8_t a = REG_GET_A(instr); |
2329 | 16 | uint8_t b = REG_GET_B(instr); |
2330 | 16 | if (R[b].type == VAL_INT) { |
2331 | 15 | reg_set(&R[a], value_int(-R[b].as.int_val)); |
2332 | 15 | } else if (R[b].type == VAL_FLOAT) { |
2333 | 1 | reg_set(&R[a], value_float(-R[b].as.float_val)); |
2334 | 1 | } else { |
2335 | 0 | RVM_ERROR("cannot negate %s", value_type_name(&R[b])); |
2336 | 0 | } |
2337 | 16 | DISPATCH(); |
2338 | 16 | } |
2339 | | |
2340 | 293 | CASE(ADDI) { |
2341 | 293 | uint8_t a = REG_GET_A(instr); |
2342 | 293 | uint8_t b = REG_GET_B(instr); |
2343 | 293 | int8_t c = (int8_t)REG_GET_C(instr); |
2344 | 293 | if (R[b].type == VAL_INT) { |
2345 | 293 | reg_set(&R[a], value_int(R[b].as.int_val + c)); |
2346 | 293 | } else if (R[b].type == VAL_FLOAT) { |
2347 | 0 | reg_set(&R[a], value_float(R[b].as.float_val + c)); |
2348 | 0 | } else { |
2349 | 0 | RVM_ERROR("cannot add immediate to %s", value_type_name(&R[b])); |
2350 | 0 | } |
2351 | 293 | DISPATCH(); |
2352 | 293 | } |
2353 | | |
2354 | 47 | CASE(CONCAT) { |
2355 | 47 | uint8_t a = REG_GET_A(instr); |
2356 | 47 | uint8_t b = REG_GET_B(instr); |
2357 | 47 | uint8_t c = REG_GET_C(instr); |
2358 | 47 | char *ls = value_display(&R[b]); |
2359 | 47 | char *rs = value_display(&R[c]); |
2360 | 47 | size_t ll = strlen(ls), rl = strlen(rs); |
2361 | 47 | char *buf = bump_alloc(vm->ephemeral, ll + rl + 1); |
2362 | 47 | memcpy(buf, ls, ll); |
2363 | 47 | memcpy(buf + ll, rs, rl); |
2364 | 47 | buf[ll + rl] = '\0'; |
2365 | 47 | free(ls); free(rs); |
2366 | 47 | LatValue v = { .type = VAL_STR, .phase = VTAG_UNPHASED, .region_id = REGION_EPHEMERAL }; |
2367 | 47 | v.as.str_val = buf; |
2368 | 47 | reg_set(&R[a], v); |
2369 | 47 | DISPATCH(); |
2370 | 47 | } |
2371 | | |
2372 | 605 | CASE(EQ) { |
2373 | 605 | uint8_t a = REG_GET_A(instr); |
2374 | 605 | uint8_t b = REG_GET_B(instr); |
2375 | 605 | uint8_t c = REG_GET_C(instr); |
2376 | 605 | reg_set(&R[a], value_bool(value_eq(&R[b], &R[c]))); |
2377 | 605 | DISPATCH(); |
2378 | 605 | } |
2379 | | |
2380 | 605 | CASE(NEQ) { |
2381 | 52 | uint8_t a = REG_GET_A(instr); |
2382 | 52 | uint8_t b = REG_GET_B(instr); |
2383 | 52 | uint8_t c = REG_GET_C(instr); |
2384 | 52 | reg_set(&R[a], value_bool(!value_eq(&R[b], &R[c]))); |
2385 | 52 | DISPATCH(); |
2386 | 52 | } |
2387 | | |
2388 | 162 | CASE(LT) { |
2389 | 162 | uint8_t a = REG_GET_A(instr); |
2390 | 162 | uint8_t b = REG_GET_B(instr); |
2391 | 162 | uint8_t c = REG_GET_C(instr); |
2392 | 162 | if (R[b].type == VAL_INT && R[c].type == VAL_INT) { |
2393 | 160 | reg_set(&R[a], value_bool(R[b].as.int_val < R[c].as.int_val)); |
2394 | 160 | } else if (R[b].type == VAL_FLOAT || R[c].type == VAL_FLOAT) { |
2395 | 2 | double lv = R[b].type == VAL_FLOAT ? R[b].as.float_val : (double)R[b].as.int_val; |
2396 | 2 | double rv = R[c].type == VAL_FLOAT ? R[c].as.float_val : (double)R[c].as.int_val; |
2397 | 2 | reg_set(&R[a], value_bool(lv < rv)); |
2398 | 2 | } else { |
2399 | 0 | RVM_ERROR("cannot compare %s < %s", |
2400 | 0 | value_type_name(&R[b]), value_type_name(&R[c])); |
2401 | 0 | } |
2402 | 162 | DISPATCH(); |
2403 | 162 | } |
2404 | | |
2405 | 24 | CASE(LTEQ) { |
2406 | 24 | uint8_t a = REG_GET_A(instr); |
2407 | 24 | uint8_t b = REG_GET_B(instr); |
2408 | 24 | uint8_t c = REG_GET_C(instr); |
2409 | 24 | if (R[b].type == VAL_INT && R[c].type == VAL_INT) { |
2410 | 24 | reg_set(&R[a], value_bool(R[b].as.int_val <= R[c].as.int_val)); |
2411 | 24 | } else if (R[b].type == VAL_FLOAT || R[c].type == VAL_FLOAT) { |
2412 | 0 | double lv = R[b].type == VAL_FLOAT ? R[b].as.float_val : (double)R[b].as.int_val; |
2413 | 0 | double rv = R[c].type == VAL_FLOAT ? R[c].as.float_val : (double)R[c].as.int_val; |
2414 | 0 | reg_set(&R[a], value_bool(lv <= rv)); |
2415 | 0 | } else { |
2416 | 0 | RVM_ERROR("cannot compare %s <= %s", |
2417 | 0 | value_type_name(&R[b]), value_type_name(&R[c])); |
2418 | 0 | } |
2419 | 24 | DISPATCH(); |
2420 | 24 | } |
2421 | | |
2422 | 267 | CASE(GT) { |
2423 | 267 | uint8_t a = REG_GET_A(instr); |
2424 | 267 | uint8_t b = REG_GET_B(instr); |
2425 | 267 | uint8_t c = REG_GET_C(instr); |
2426 | 267 | if (R[b].type == VAL_INT && R[c].type == VAL_INT) { |
2427 | 264 | reg_set(&R[a], value_bool(R[b].as.int_val > R[c].as.int_val)); |
2428 | 264 | } else if (R[b].type == VAL_FLOAT || R[c].type == VAL_FLOAT) { |
2429 | 3 | double lv = R[b].type == VAL_FLOAT ? R[b].as.float_val : (double)R[b].as.int_val; |
2430 | 3 | double rv = R[c].type == VAL_FLOAT ? R[c].as.float_val : (double)R[c].as.int_val; |
2431 | 3 | reg_set(&R[a], value_bool(lv > rv)); |
2432 | 3 | } else { |
2433 | 0 | RVM_ERROR("cannot compare %s > %s", |
2434 | 0 | value_type_name(&R[b]), value_type_name(&R[c])); |
2435 | 0 | } |
2436 | 267 | DISPATCH(); |
2437 | 267 | } |
2438 | | |
2439 | 212 | CASE(GTEQ) { |
2440 | 212 | uint8_t a = REG_GET_A(instr); |
2441 | 212 | uint8_t b = REG_GET_B(instr); |
2442 | 212 | uint8_t c = REG_GET_C(instr); |
2443 | 212 | if (R[b].type == VAL_INT && R[c].type == VAL_INT) { |
2444 | 211 | reg_set(&R[a], value_bool(R[b].as.int_val >= R[c].as.int_val)); |
2445 | 211 | } else if (R[b].type == VAL_FLOAT || R[c].type == VAL_FLOAT) { |
2446 | 1 | double lv = R[b].type == VAL_FLOAT ? R[b].as.float_val : (double)R[b].as.int_val; |
2447 | 1 | double rv = R[c].type == VAL_FLOAT ? R[c].as.float_val : (double)R[c].as.int_val; |
2448 | 1 | reg_set(&R[a], value_bool(lv >= rv)); |
2449 | 1 | } else { |
2450 | 0 | RVM_ERROR("cannot compare %s >= %s", |
2451 | 0 | value_type_name(&R[b]), value_type_name(&R[c])); |
2452 | 0 | } |
2453 | 212 | DISPATCH(); |
2454 | 212 | } |
2455 | | |
2456 | 32 | CASE(NOT) { |
2457 | 32 | uint8_t a = REG_GET_A(instr); |
2458 | 32 | uint8_t b = REG_GET_B(instr); |
2459 | 32 | reg_set(&R[a], value_bool(!value_is_truthy(&R[b]))); |
2460 | 32 | DISPATCH(); |
2461 | 32 | } |
2462 | | |
2463 | 912 | CASE(JMP) { |
2464 | 912 | int32_t offset = REG_GET_sBx24(instr); |
2465 | 912 | frame->ip += offset; |
2466 | 912 | DISPATCH(); |
2467 | 912 | } |
2468 | | |
2469 | 1.90k | CASE(JMPFALSE) { |
2470 | 1.90k | uint8_t a = REG_GET_A(instr); |
2471 | 1.90k | int16_t offset = REG_GET_sBx(instr); |
2472 | 1.90k | if (!value_is_truthy(&R[a])) |
2473 | 1.09k | frame->ip += offset; |
2474 | 1.90k | DISPATCH(); |
2475 | 1.90k | } |
2476 | | |
2477 | 1.90k | CASE(JMPTRUE) { |
2478 | 20 | uint8_t a = REG_GET_A(instr); |
2479 | 20 | int16_t offset = REG_GET_sBx(instr); |
2480 | 20 | if (value_is_truthy(&R[a])) |
2481 | 12 | frame->ip += offset; |
2482 | 20 | DISPATCH(); |
2483 | 20 | } |
2484 | | |
2485 | 2.82k | CASE(GETGLOBAL) { |
2486 | 2.82k | uint8_t a = REG_GET_A(instr); |
2487 | 2.82k | uint16_t bx = REG_GET_Bx(instr); |
2488 | 2.82k | const char *name = frame->chunk->constants[bx].as.str_val; |
2489 | 2.82k | LatValue val; |
2490 | 2.82k | if (!env_get(vm->env, name, &val)) |
2491 | 4 | RVM_ERROR("undefined variable '%s'", name); |
2492 | 2.82k | reg_set(&R[a], rvm_clone(&val)); |
2493 | 2.82k | DISPATCH(); |
2494 | 2.82k | } |
2495 | | |
2496 | 11 | CASE(SETGLOBAL) { |
2497 | 11 | uint8_t a = REG_GET_A(instr); |
2498 | 11 | uint16_t bx = REG_GET_Bx(instr); |
2499 | 11 | const char *name = frame->chunk->constants[bx].as.str_val; |
2500 | 11 | if (!env_set(vm->env, name, rvm_clone(&R[a]))) |
2501 | 0 | RVM_ERROR("undefined variable '%s'", name); |
2502 | | /* Record history for tracked globals */ |
2503 | 11 | { |
2504 | 11 | if (vm->rt->tracking_active) |
2505 | 0 | rt_record_history(vm->rt, name, &R[a]); |
2506 | 11 | } |
2507 | 11 | DISPATCH(); |
2508 | 11 | } |
2509 | | |
2510 | 2.86k | CASE(DEFINEGLOBAL) { |
2511 | 2.86k | uint8_t a = REG_GET_A(instr); |
2512 | 2.86k | uint16_t bx = REG_GET_Bx(instr); |
2513 | 2.86k | const char *name = frame->chunk->constants[bx].as.str_val; |
2514 | 2.86k | LatValue val = rvm_clone(&R[a]); |
2515 | | |
2516 | | /* Phase-dispatch overloading: if defining a phase-constrained |
2517 | | * closure and one already exists, create an overload array */ |
2518 | 2.86k | if (val.type == VAL_CLOSURE && val.as.closure.native_fn != NULL && |
2519 | 2.86k | val.as.closure.default_values != VM_NATIVE_MARKER) { |
2520 | 2.65k | uint32_t magic; |
2521 | 2.65k | memcpy(&magic, val.as.closure.native_fn, sizeof(uint32_t)); |
2522 | 2.65k | if (magic == REGCHUNK_MAGIC) { |
2523 | 2.65k | RegChunk *ch = (RegChunk *)val.as.closure.native_fn; |
2524 | 2.65k | if (ch->param_phases) { |
2525 | 13 | LatValue existing; |
2526 | 13 | if (env_get(vm->env, name, &existing)) { |
2527 | 2 | if (existing.type == VAL_CLOSURE && existing.as.closure.native_fn != NULL && |
2528 | 2 | existing.as.closure.default_values != VM_NATIVE_MARKER) { |
2529 | 2 | uint32_t emag; |
2530 | 2 | memcpy(&emag, existing.as.closure.native_fn, sizeof(uint32_t)); |
2531 | 2 | if (emag == REGCHUNK_MAGIC) { |
2532 | 2 | RegChunk *ech = (RegChunk *)existing.as.closure.native_fn; |
2533 | 2 | if (ech->param_phases) { |
2534 | 2 | LatValue elems[2] = { value_deep_clone(&existing), val }; |
2535 | 2 | LatValue arr = value_array(elems, 2); |
2536 | 2 | env_define(vm->env, name, arr); |
2537 | 2 | DISPATCH(); |
2538 | 2 | } |
2539 | 2 | } |
2540 | 2 | } else if (existing.type == VAL_ARRAY) { |
2541 | 0 | size_t new_len = existing.as.array.len + 1; |
2542 | 0 | LatValue *new_elems = malloc(new_len * sizeof(LatValue)); |
2543 | 0 | for (size_t i = 0; i < existing.as.array.len; i++) |
2544 | 0 | new_elems[i] = value_deep_clone(&existing.as.array.elems[i]); |
2545 | 0 | new_elems[existing.as.array.len] = val; |
2546 | 0 | LatValue arr = value_array(new_elems, new_len); |
2547 | 0 | free(new_elems); |
2548 | 0 | env_define(vm->env, name, arr); |
2549 | 0 | DISPATCH(); |
2550 | 0 | } |
2551 | 2 | } |
2552 | 13 | } |
2553 | 2.65k | } |
2554 | 2.65k | } |
2555 | | |
2556 | 2.86k | env_define(vm->env, name, val); |
2557 | 2.86k | DISPATCH(); |
2558 | 2.86k | } |
2559 | | |
2560 | 2.86k | CASE(GETFIELD) { |
2561 | 291 | uint8_t a = REG_GET_A(instr); |
2562 | 291 | uint8_t b = REG_GET_B(instr); |
2563 | 291 | uint8_t c = REG_GET_C(instr); |
2564 | 291 | const char *field_name = frame->chunk->constants[c].as.str_val; |
2565 | | |
2566 | 291 | if (R[b].type == VAL_STRUCT) { |
2567 | 260 | bool found = false; |
2568 | 331 | for (size_t i = 0; i < R[b].as.strct.field_count; i++) { |
2569 | 331 | if (strcmp(R[b].as.strct.field_names[i], field_name) == 0) { |
2570 | 260 | reg_set(&R[a], rvm_clone(&R[b].as.strct.field_values[i])); |
2571 | 260 | found = true; |
2572 | 260 | break; |
2573 | 260 | } |
2574 | 331 | } |
2575 | 260 | if (!found) |
2576 | 0 | RVM_ERROR("struct '%s' has no field '%s'", |
2577 | 260 | R[b].as.strct.name, field_name); |
2578 | 260 | } else if (R[b].type == VAL_MAP) { |
2579 | 25 | LatValue *val = lat_map_get(R[b].as.map.map, field_name); |
2580 | 25 | if (val) |
2581 | 22 | reg_set(&R[a], rvm_clone(val)); |
2582 | 3 | else |
2583 | 3 | reg_set(&R[a], value_nil()); |
2584 | 25 | } else if (R[b].type == VAL_TUPLE) { |
2585 | 6 | char *endp; |
2586 | 6 | long idx = strtol(field_name, &endp, 10); |
2587 | 6 | if (*endp == '\0' && idx >= 0 && (size_t)idx < R[b].as.tuple.len) |
2588 | 6 | reg_set(&R[a], rvm_clone(&R[b].as.tuple.elems[idx])); |
2589 | 0 | else |
2590 | 0 | RVM_ERROR("tuple has no field '%s'", field_name); |
2591 | 6 | } else if (R[b].type == VAL_ENUM) { |
2592 | 0 | if (strcmp(field_name, "tag") == 0 || strcmp(field_name, "variant_name") == 0) |
2593 | 0 | reg_set(&R[a], value_string(R[b].as.enm.variant_name)); |
2594 | 0 | else if (strcmp(field_name, "enum_name") == 0) |
2595 | 0 | reg_set(&R[a], value_string(R[b].as.enm.enum_name)); |
2596 | 0 | else if (strcmp(field_name, "payload") == 0) { |
2597 | 0 | if (R[b].as.enm.payload_count > 0) { |
2598 | 0 | LatValue *elems = malloc(R[b].as.enm.payload_count * sizeof(LatValue)); |
2599 | 0 | for (size_t pi = 0; pi < R[b].as.enm.payload_count; pi++) |
2600 | 0 | elems[pi] = rvm_clone(&R[b].as.enm.payload[pi]); |
2601 | 0 | reg_set(&R[a], value_array(elems, R[b].as.enm.payload_count)); |
2602 | 0 | free(elems); |
2603 | 0 | } else { |
2604 | 0 | reg_set(&R[a], value_array(NULL, 0)); |
2605 | 0 | } |
2606 | 0 | } else |
2607 | 0 | RVM_ERROR("enum has no field '%s'", field_name); |
2608 | 0 | } else { |
2609 | 0 | RVM_ERROR("cannot access field '%s' on %s", |
2610 | 0 | field_name, value_type_name(&R[b])); |
2611 | 0 | } |
2612 | 291 | DISPATCH(); |
2613 | 291 | } |
2614 | | |
2615 | 15 | CASE(SETFIELD) { |
2616 | 15 | uint8_t a = REG_GET_A(instr); /* object reg */ |
2617 | 15 | uint8_t b = REG_GET_B(instr); /* field name constant */ |
2618 | 15 | uint8_t c = REG_GET_C(instr); /* value reg */ |
2619 | 15 | const char *field_name = frame->chunk->constants[b].as.str_val; |
2620 | | |
2621 | | /* Phase checks */ |
2622 | 15 | if (R[a].phase == VTAG_CRYSTAL) { |
2623 | | /* Check per-field phases for structs with partial freeze (freeze except) */ |
2624 | 3 | bool blocked = true; |
2625 | 3 | if (R[a].type == VAL_STRUCT && R[a].as.strct.field_phases) { |
2626 | 4 | for (size_t i = 0; i < R[a].as.strct.field_count; i++) { |
2627 | 4 | if (strcmp(R[a].as.strct.field_names[i], field_name) == 0) { |
2628 | 2 | if (R[a].as.strct.field_phases[i] != VTAG_CRYSTAL) blocked = false; |
2629 | 2 | break; |
2630 | 2 | } |
2631 | 4 | } |
2632 | 2 | } |
2633 | 3 | if (blocked) |
2634 | 2 | RVM_ERROR("cannot set field '%s' on a frozen value", field_name); |
2635 | 3 | } |
2636 | | /* Also check per-field phases (alloy types) even on non-frozen structs */ |
2637 | 13 | if (R[a].type == VAL_STRUCT && R[a].as.strct.field_phases && |
2638 | 13 | R[a].phase != VTAG_CRYSTAL) { |
2639 | 8 | for (size_t i = 0; i < R[a].as.strct.field_count; i++) { |
2640 | 8 | if (strcmp(R[a].as.strct.field_names[i], field_name) == 0) { |
2641 | 5 | if (R[a].as.strct.field_phases[i] == VTAG_CRYSTAL) |
2642 | 2 | RVM_ERROR("cannot assign to frozen field '%s'", field_name); |
2643 | 3 | break; |
2644 | 5 | } |
2645 | 8 | } |
2646 | 5 | } |
2647 | | |
2648 | 11 | if (R[a].type == VAL_STRUCT) { |
2649 | 19 | for (size_t i = 0; i < R[a].as.strct.field_count; i++) { |
2650 | 19 | if (strcmp(R[a].as.strct.field_names[i], field_name) == 0) { |
2651 | 11 | value_free(&R[a].as.strct.field_values[i]); |
2652 | 11 | R[a].as.strct.field_values[i] = rvm_clone(&R[c]); |
2653 | 11 | break; |
2654 | 11 | } |
2655 | 19 | } |
2656 | 11 | } else if (R[a].type == VAL_MAP) { |
2657 | 0 | LatValue cloned = rvm_clone(&R[c]); |
2658 | 0 | lat_map_set(R[a].as.map.map, field_name, &cloned); |
2659 | 0 | } |
2660 | 11 | DISPATCH(); |
2661 | 11 | } |
2662 | | |
2663 | 331 | CASE(GETINDEX) { |
2664 | 331 | uint8_t a = REG_GET_A(instr); |
2665 | 331 | uint8_t b = REG_GET_B(instr); |
2666 | 331 | uint8_t c = REG_GET_C(instr); |
2667 | | |
2668 | 331 | if (R[b].type == VAL_ARRAY && R[c].type == VAL_RANGE) { |
2669 | | /* Array range slicing: arr[start..end] */ |
2670 | 3 | int64_t start = R[c].as.range.start; |
2671 | 3 | int64_t end = R[c].as.range.end; |
2672 | 3 | size_t len = R[b].as.array.len; |
2673 | 3 | if (start < 0) start = 0; |
2674 | 3 | if ((size_t)start > len) start = (int64_t)len; |
2675 | 3 | if (end < 0) end = 0; |
2676 | 3 | if ((size_t)end > len) end = (int64_t)len; |
2677 | 3 | if (start >= end) { |
2678 | 1 | reg_set(&R[a], value_array(NULL, 0)); |
2679 | 2 | } else { |
2680 | 2 | size_t slice_len = (size_t)(end - start); |
2681 | 2 | LatValue *elems = malloc(slice_len * sizeof(LatValue)); |
2682 | 8 | for (size_t i = 0; i < slice_len; i++) |
2683 | 6 | elems[i] = rvm_clone(&R[b].as.array.elems[start + (int64_t)i]); |
2684 | 2 | reg_set(&R[a], value_array(elems, slice_len)); |
2685 | 2 | free(elems); |
2686 | 2 | } |
2687 | 328 | } else if (R[b].type == VAL_STR && R[c].type == VAL_RANGE) { |
2688 | | /* String range slicing: str[start..end] */ |
2689 | 2 | int64_t start = R[c].as.range.start; |
2690 | 2 | int64_t end = R[c].as.range.end; |
2691 | 2 | size_t len = strlen(R[b].as.str_val); |
2692 | 2 | if (start < 0) start = 0; |
2693 | 2 | if ((size_t)start > len) start = (int64_t)len; |
2694 | 2 | if (end < 0) end = 0; |
2695 | 2 | if ((size_t)end > len) end = (int64_t)len; |
2696 | 2 | if (start >= end) { |
2697 | 0 | reg_set(&R[a], value_string("")); |
2698 | 2 | } else { |
2699 | 2 | size_t slice_len = (size_t)(end - start); |
2700 | 2 | char *slice = malloc(slice_len + 1); |
2701 | 2 | memcpy(slice, R[b].as.str_val + start, slice_len); |
2702 | 2 | slice[slice_len] = '\0'; |
2703 | 2 | reg_set(&R[a], value_string_owned(slice)); |
2704 | 2 | } |
2705 | 326 | } else if (R[b].type == VAL_ARRAY) { |
2706 | 250 | if (R[c].type != VAL_INT) |
2707 | 0 | RVM_ERROR("array index must be an integer"); |
2708 | 250 | int64_t idx = R[c].as.int_val; |
2709 | 250 | if (idx < 0) idx += (int64_t)R[b].as.array.len; |
2710 | 250 | if (idx < 0 || (size_t)idx >= R[b].as.array.len) |
2711 | 0 | RVM_ERROR("array index %lld out of bounds (len %zu)", |
2712 | 250 | (long long)R[c].as.int_val, R[b].as.array.len); |
2713 | 250 | reg_set(&R[a], rvm_clone(&R[b].as.array.elems[idx])); |
2714 | 250 | } else if (R[b].type == VAL_MAP) { |
2715 | 40 | if (R[c].type != VAL_STR) |
2716 | 0 | RVM_ERROR("map key must be a string"); |
2717 | 40 | LatValue *val = lat_map_get(R[b].as.map.map, R[c].as.str_val); |
2718 | 40 | if (val) |
2719 | 40 | reg_set(&R[a], rvm_clone(val)); |
2720 | 0 | else |
2721 | 0 | reg_set(&R[a], value_nil()); |
2722 | 40 | } else if (R[b].type == VAL_STR) { |
2723 | 3 | if (R[c].type != VAL_INT) |
2724 | 0 | RVM_ERROR("string index must be an integer"); |
2725 | 3 | int64_t idx = R[c].as.int_val; |
2726 | 3 | size_t len = strlen(R[b].as.str_val); |
2727 | 3 | if (idx < 0) idx += (int64_t)len; |
2728 | 3 | if (idx < 0 || (size_t)idx >= len) |
2729 | 0 | RVM_ERROR("string index out of bounds"); |
2730 | 3 | char buf[2] = { R[b].as.str_val[idx], '\0' }; |
2731 | 3 | reg_set(&R[a], value_string(buf)); |
2732 | 33 | } else if (R[b].type == VAL_BUFFER) { |
2733 | 28 | if (R[c].type != VAL_INT) |
2734 | 0 | RVM_ERROR("buffer index must be an integer"); |
2735 | 28 | int64_t idx = R[c].as.int_val; |
2736 | 28 | if (idx < 0 || (size_t)idx >= R[b].as.buffer.len) |
2737 | 0 | RVM_ERROR("buffer index out of bounds"); |
2738 | 28 | reg_set(&R[a], value_int((int64_t)R[b].as.buffer.data[idx])); |
2739 | 28 | } else if (R[b].type == VAL_REF) { |
2740 | | /* Proxy indexing on Ref inner value */ |
2741 | 5 | LatRef *ref = R[b].as.ref.ref; |
2742 | 5 | if (ref->value.type == VAL_MAP) { |
2743 | 3 | if (R[c].type != VAL_STR) RVM_ERROR("map key must be a string"); |
2744 | 3 | LatValue *val = lat_map_get(ref->value.as.map.map, R[c].as.str_val); |
2745 | 3 | reg_set(&R[a], val ? rvm_clone(val) : value_nil()); |
2746 | 3 | } else if (ref->value.type == VAL_ARRAY) { |
2747 | 2 | if (R[c].type != VAL_INT) RVM_ERROR("array index must be an integer"); |
2748 | 2 | int64_t idx = R[c].as.int_val; |
2749 | 2 | if (idx < 0) idx += (int64_t)ref->value.as.array.len; |
2750 | 2 | if (idx < 0 || (size_t)idx >= ref->value.as.array.len) |
2751 | 0 | RVM_ERROR("array index out of bounds"); |
2752 | 2 | reg_set(&R[a], rvm_clone(&ref->value.as.array.elems[idx])); |
2753 | 2 | } else { |
2754 | 0 | RVM_ERROR("cannot index Ref(%s)", value_type_name(&ref->value)); |
2755 | 0 | } |
2756 | 5 | } else { |
2757 | 0 | RVM_ERROR("cannot index %s", value_type_name(&R[b])); |
2758 | 0 | } |
2759 | 331 | DISPATCH(); |
2760 | 331 | } |
2761 | | |
2762 | 8 | CASE(SETINDEX) { |
2763 | 8 | uint8_t a = REG_GET_A(instr); /* object */ |
2764 | 8 | uint8_t b = REG_GET_B(instr); /* index */ |
2765 | 8 | uint8_t c = REG_GET_C(instr); /* value */ |
2766 | | |
2767 | | /* Phase checks for mutation */ |
2768 | 8 | if (R[a].phase == VTAG_CRYSTAL) { |
2769 | | /* Allow mutation on maps with per-key phases (freeze except) if key is not frozen */ |
2770 | 1 | bool blocked = true; |
2771 | 1 | if (R[a].type == VAL_MAP && R[b].type == VAL_STR && R[a].as.map.key_phases) { |
2772 | 0 | PhaseTag *kp = lat_map_get(R[a].as.map.key_phases, R[b].as.str_val); |
2773 | 0 | if (!kp || *kp != VTAG_CRYSTAL) blocked = false; |
2774 | 0 | } |
2775 | 1 | if (blocked) |
2776 | 1 | RVM_ERROR("cannot modify a frozen value"); |
2777 | 1 | } |
2778 | 7 | if (R[a].phase == VTAG_SUBLIMATED && R[a].type == VAL_MAP) |
2779 | 0 | RVM_ERROR("cannot add keys to a sublimated map"); |
2780 | | /* Per-key phase check for non-frozen maps */ |
2781 | 7 | if (R[a].type == VAL_MAP && R[b].type == VAL_STR && R[a].as.map.key_phases) { |
2782 | 0 | PhaseTag *kp = lat_map_get(R[a].as.map.key_phases, R[b].as.str_val); |
2783 | 0 | if (kp && *kp == VTAG_CRYSTAL) |
2784 | 0 | RVM_ERROR("cannot modify frozen key '%s'", R[b].as.str_val); |
2785 | 0 | } |
2786 | | |
2787 | 7 | if (R[a].type == VAL_ARRAY) { |
2788 | 0 | if (R[b].type != VAL_INT) |
2789 | 0 | RVM_ERROR("array index must be an integer"); |
2790 | 0 | int64_t idx = R[b].as.int_val; |
2791 | 0 | if (idx < 0) idx += (int64_t)R[a].as.array.len; |
2792 | 0 | if (idx < 0 || (size_t)idx >= R[a].as.array.len) |
2793 | 0 | RVM_ERROR("array index out of bounds"); |
2794 | 0 | value_free(&R[a].as.array.elems[idx]); |
2795 | 0 | R[a].as.array.elems[idx] = rvm_clone(&R[c]); |
2796 | 7 | } else if (R[a].type == VAL_MAP) { |
2797 | 0 | if (R[b].type != VAL_STR) |
2798 | 0 | RVM_ERROR("map key must be a string"); |
2799 | 0 | LatValue cloned = rvm_clone(&R[c]); |
2800 | 0 | lat_map_set(R[a].as.map.map, R[b].as.str_val, &cloned); |
2801 | 7 | } else if (R[a].type == VAL_BUFFER) { |
2802 | 2 | if (R[b].type != VAL_INT) RVM_ERROR("buffer index must be an integer"); |
2803 | 2 | int64_t idx = R[b].as.int_val; |
2804 | 2 | if (idx < 0 || (size_t)idx >= R[a].as.buffer.len) |
2805 | 0 | RVM_ERROR("buffer index out of bounds"); |
2806 | 2 | if (R[c].type != VAL_INT) RVM_ERROR("buffer value must be an integer"); |
2807 | 2 | R[a].as.buffer.data[(size_t)idx] = (uint8_t)(R[c].as.int_val & 0xFF); |
2808 | 5 | } else if (R[a].type == VAL_REF) { |
2809 | | /* Proxy: set index on inner value */ |
2810 | 5 | LatRef *ref = R[a].as.ref.ref; |
2811 | 5 | if (ref->value.type == VAL_MAP) { |
2812 | 4 | if (R[b].type != VAL_STR) RVM_ERROR("map key must be a string"); |
2813 | 4 | LatValue cloned = rvm_clone(&R[c]); |
2814 | 4 | lat_map_set(ref->value.as.map.map, R[b].as.str_val, &cloned); |
2815 | 4 | } else if (ref->value.type == VAL_ARRAY) { |
2816 | 1 | if (R[b].type != VAL_INT) RVM_ERROR("array index must be an integer"); |
2817 | 1 | int64_t idx = R[b].as.int_val; |
2818 | 1 | if (idx < 0) idx += (int64_t)ref->value.as.array.len; |
2819 | 1 | if (idx < 0 || (size_t)idx >= ref->value.as.array.len) |
2820 | 0 | RVM_ERROR("array index out of bounds"); |
2821 | 1 | value_free(&ref->value.as.array.elems[idx]); |
2822 | 1 | ref->value.as.array.elems[idx] = rvm_clone(&R[c]); |
2823 | 1 | } else { |
2824 | 0 | RVM_ERROR("cannot set index on Ref(%s)", value_type_name(&ref->value)); |
2825 | 0 | } |
2826 | 5 | } else { |
2827 | 0 | RVM_ERROR("cannot set index on %s", value_type_name(&R[a])); |
2828 | 0 | } |
2829 | 7 | DISPATCH(); |
2830 | 7 | } |
2831 | | |
2832 | 696 | CASE(GETUPVALUE) { |
2833 | 696 | uint8_t a = REG_GET_A(instr); |
2834 | 696 | uint8_t b = REG_GET_B(instr); |
2835 | 696 | if (frame->upvalues && b < frame->upvalue_count) { |
2836 | 696 | reg_set(&R[a], rvm_clone(frame->upvalues[b]->location)); |
2837 | 696 | } |
2838 | 696 | DISPATCH(); |
2839 | 696 | } |
2840 | | |
2841 | 696 | CASE(SETUPVALUE) { |
2842 | 0 | uint8_t a = REG_GET_A(instr); |
2843 | 0 | uint8_t b = REG_GET_B(instr); |
2844 | 0 | if (frame->upvalues && b < frame->upvalue_count) { |
2845 | 0 | value_free(frame->upvalues[b]->location); |
2846 | 0 | *frame->upvalues[b]->location = rvm_clone(&R[a]); |
2847 | 0 | } |
2848 | 0 | DISPATCH(); |
2849 | 0 | } |
2850 | |
|
2851 | 0 | CASE(CLOSEUPVALUE) { |
2852 | | /* Close upvalue at register A */ |
2853 | 0 | uint8_t a = REG_GET_A(instr); |
2854 | 0 | ObjUpvalue *prev = NULL; |
2855 | 0 | ObjUpvalue *uv = vm->open_upvalues; |
2856 | 0 | while (uv) { |
2857 | 0 | if (uv->location == &R[a]) { |
2858 | 0 | uv->closed = rvm_clone(&R[a]); |
2859 | 0 | uv->location = &uv->closed; |
2860 | 0 | if (prev) prev->next = uv->next; |
2861 | 0 | else vm->open_upvalues = uv->next; |
2862 | 0 | break; |
2863 | 0 | } |
2864 | 0 | prev = uv; |
2865 | 0 | uv = uv->next; |
2866 | 0 | } |
2867 | 0 | DISPATCH(); |
2868 | 0 | } |
2869 | |
|
2870 | 3.14k | CASE(CALL) { |
2871 | 3.14k | uint8_t a = REG_GET_A(instr); /* func register */ |
2872 | 3.14k | uint8_t b = REG_GET_B(instr); /* arg count */ |
2873 | 3.14k | uint8_t c = REG_GET_C(instr); /* return count (1 for now) */ |
2874 | 3.14k | (void)c; |
2875 | | |
2876 | 3.14k | LatValue *func = &R[a]; |
2877 | | |
2878 | | /* Phase-dispatch overload resolution: VAL_ARRAY of closures */ |
2879 | 3.14k | if (func->type == VAL_ARRAY) { |
2880 | 2 | int best_score = -1; |
2881 | 2 | int best_idx = -1; |
2882 | 6 | for (size_t ci = 0; ci < func->as.array.len; ci++) { |
2883 | 4 | LatValue *cand = &func->as.array.elems[ci]; |
2884 | 4 | if (cand->type != VAL_CLOSURE || cand->as.closure.native_fn == NULL) continue; |
2885 | 4 | if (cand->as.closure.default_values == VM_NATIVE_MARKER) continue; |
2886 | 4 | uint32_t cmag; |
2887 | 4 | memcpy(&cmag, cand->as.closure.native_fn, sizeof(uint32_t)); |
2888 | 4 | if (cmag != REGCHUNK_MAGIC) continue; |
2889 | 4 | RegChunk *ch = (RegChunk *)cand->as.closure.native_fn; |
2890 | 4 | if (!ch->param_phases) continue; |
2891 | 4 | bool compatible = true; |
2892 | 4 | int score = 0; |
2893 | 6 | for (int j = 0; j < ch->param_phase_count && j < (int)b; j++) { |
2894 | 4 | uint8_t pp = ch->param_phases[j]; |
2895 | 4 | LatValue *arg = &R[a + 1 + j]; |
2896 | 4 | if (pp == PHASE_FLUID) { |
2897 | 2 | if (arg->phase == VTAG_CRYSTAL) { compatible = false; break; } |
2898 | 1 | if (arg->phase == VTAG_FLUID) score += 3; |
2899 | 0 | else score += 1; |
2900 | 2 | } else if (pp == PHASE_CRYSTAL) { |
2901 | 2 | if (arg->phase == VTAG_FLUID) { compatible = false; break; } |
2902 | 1 | if (arg->phase == VTAG_CRYSTAL) score += 3; |
2903 | 0 | else score += 1; |
2904 | 1 | } else { |
2905 | 0 | if (arg->phase == VTAG_UNPHASED) score += 2; |
2906 | 0 | else score += 1; |
2907 | 0 | } |
2908 | 4 | } |
2909 | 4 | if (compatible && score > best_score) { |
2910 | 2 | best_score = score; |
2911 | 2 | best_idx = (int)ci; |
2912 | 2 | } |
2913 | 4 | } |
2914 | 2 | if (best_idx >= 0) { |
2915 | 2 | LatValue matched = value_deep_clone(&func->as.array.elems[best_idx]); |
2916 | 2 | reg_set(func, matched); |
2917 | 2 | } else { |
2918 | 0 | RVM_ERROR("no matching overload for given argument phases"); |
2919 | 0 | } |
2920 | 2 | } |
2921 | | |
2922 | 3.14k | if (func->type != VAL_CLOSURE) |
2923 | 0 | RVM_ERROR("attempt to call a non-function (%s)", |
2924 | 3.14k | value_type_name(func)); |
2925 | | |
2926 | | /* Check for native function */ |
2927 | 3.14k | if (func->as.closure.default_values == VM_NATIVE_MARKER) { |
2928 | 1.49k | VMNativeFn native = (VMNativeFn)func->as.closure.native_fn; |
2929 | | |
2930 | | /* Sync named locals from current call chain to env. |
2931 | | * Needed for natives that access variables by name (track, react, bond, seed, etc.) |
2932 | | * and for phase system operations that read env. */ |
2933 | 1.49k | { |
2934 | 5.77k | for (int fi = 0; fi < vm->frame_count; fi++) { |
2935 | 4.28k | RegCallFrame *sf = &vm->frames[fi]; |
2936 | 4.28k | if (!sf->chunk || !sf->chunk->local_names) continue; |
2937 | 46.6k | for (size_t li = 0; li < sf->chunk->local_name_cap; li++) { |
2938 | 44.2k | if (sf->chunk->local_names[li] && sf->chunk->local_names[li][0]) { |
2939 | 9.62k | LatValue clone = value_deep_clone(&sf->regs[li]); |
2940 | 9.62k | if (!env_set(vm->env, sf->chunk->local_names[li], clone)) |
2941 | 1.32k | env_define(vm->env, sf->chunk->local_names[li], clone); |
2942 | 9.62k | } |
2943 | 44.2k | } |
2944 | 2.40k | } |
2945 | 1.49k | } |
2946 | | /* Collect args */ |
2947 | 1.49k | LatValue args[16]; |
2948 | 2.67k | for (int i = 0; i < b; i++) |
2949 | 1.18k | args[i] = rvm_clone(&R[a + 1 + i]); |
2950 | 1.49k | LatValue ret = native(args, b); |
2951 | 2.67k | for (int i = 0; i < b; i++) |
2952 | 1.18k | value_free(&args[i]); |
2953 | | /* Check runtime for native errors */ |
2954 | 1.49k | if (vm->rt->error) { |
2955 | 60 | char *err = vm->rt->error; |
2956 | 60 | vm->rt->error = NULL; |
2957 | 60 | value_free(&ret); |
2958 | 60 | RVM_ERROR("%s", err); |
2959 | 60 | } |
2960 | | /* Also check if regvm itself got an error (from re-entrant dispatch callbacks) */ |
2961 | 1.44k | if (vm->error) { |
2962 | 0 | value_free(&ret); |
2963 | 0 | return REGVM_RUNTIME_ERROR; |
2964 | 0 | } |
2965 | | /* Note: no reverse env→locals sync here — it's too broad and can break |
2966 | | * closure-captured values. grow() and similar natives that modify variables |
2967 | | * by name will need specialized handling if needed. */ |
2968 | 1.44k | reg_set(&R[a], ret); |
2969 | 1.44k | DISPATCH(); |
2970 | 1.44k | } |
2971 | | |
2972 | | /* Extension native function (loaded via require_ext) */ |
2973 | 3.09k | if (func->as.closure.default_values == VM_EXT_MARKER) { |
2974 | 64 | LatValue args[16]; |
2975 | 166 | for (int i = 0; i < b; i++) |
2976 | 102 | args[i] = rvm_clone(&R[a + 1 + i]); |
2977 | 64 | LatValue ret = ext_call_native(func->as.closure.native_fn, args, (size_t)b); |
2978 | 166 | for (int i = 0; i < b; i++) |
2979 | 102 | value_free(&args[i]); |
2980 | | /* Extension errors return strings prefixed with "EVAL_ERROR:" */ |
2981 | 64 | if (ret.type == VAL_STR && ret.as.str_val && |
2982 | 64 | strncmp(ret.as.str_val, "EVAL_ERROR:", 11) == 0) { |
2983 | 3 | char *msg = strdup(ret.as.str_val + 11); |
2984 | 3 | value_free(&ret); |
2985 | 3 | RVM_ERROR("%s", msg); |
2986 | 3 | } |
2987 | 61 | reg_set(&R[a], ret); |
2988 | 61 | DISPATCH(); |
2989 | 61 | } |
2990 | | |
2991 | | /* Compiled function call */ |
2992 | 3.09k | RegChunk *fn_chunk = (RegChunk *)func->as.closure.native_fn; |
2993 | 3.09k | if (!fn_chunk) |
2994 | 0 | RVM_ERROR("attempt to call a closure with NULL chunk"); |
2995 | | |
2996 | | /* Guard: detect stack-VM closures (from require()) that can't run in regvm. |
2997 | | * RegChunks have a magic header; stack-VM Chunks don't. |
2998 | | * Use memcpy to avoid misaligned read when fn_chunk is actually a stack Chunk. */ |
2999 | 3.09k | { |
3000 | 3.09k | uint32_t magic; |
3001 | 3.09k | memcpy(&magic, fn_chunk, sizeof(uint32_t)); |
3002 | 3.09k | if (magic != REGCHUNK_MAGIC) |
3003 | 0 | RVM_ERROR("cannot call stack-VM closure from register VM " |
3004 | 3.09k | "(use 'import' instead of 'require')"); |
3005 | 3.09k | } |
3006 | | |
3007 | | /* Phase constraint check on parameters */ |
3008 | 3.09k | if (fn_chunk->param_phases) { |
3009 | 17 | for (int i = 0; i < fn_chunk->param_phase_count && i < (int)b; i++) { |
3010 | 10 | uint8_t pp = fn_chunk->param_phases[i]; |
3011 | 10 | if (pp == PHASE_UNSPECIFIED) continue; |
3012 | 10 | LatValue *arg = &R[a + 1 + i]; |
3013 | 10 | if (pp == PHASE_FLUID && arg->phase == VTAG_CRYSTAL) { |
3014 | 2 | RVM_ERROR("phase constraint violation in function '%s'", |
3015 | 2 | fn_chunk->name ? fn_chunk->name : "<anonymous>"); |
3016 | 2 | } |
3017 | 8 | if (pp == PHASE_CRYSTAL && arg->phase == VTAG_FLUID) { |
3018 | 1 | RVM_ERROR("phase constraint violation in function '%s'", |
3019 | 1 | fn_chunk->name ? fn_chunk->name : "<anonymous>"); |
3020 | 1 | } |
3021 | 8 | } |
3022 | 10 | } |
3023 | | |
3024 | 3.08k | if (vm->frame_count >= REGVM_FRAMES_MAX) |
3025 | 0 | RVM_ERROR("call stack overflow"); |
3026 | | |
3027 | | /* Allocate new register window */ |
3028 | 3.08k | size_t new_base = vm->reg_stack_top; |
3029 | 3.08k | if (new_base + REGVM_REG_MAX > REGVM_REG_MAX * REGVM_FRAMES_MAX) |
3030 | 0 | RVM_ERROR("register stack overflow"); |
3031 | | |
3032 | 3.08k | LatValue *new_regs = &vm->reg_stack[new_base]; |
3033 | 3.08k | vm->reg_stack_top += REGVM_REG_MAX; |
3034 | | |
3035 | | /* Initialize new registers to nil */ |
3036 | 408k | for (int i = 0; i < REGVM_REG_MAX; i++) |
3037 | 405k | new_regs[i] = value_nil(); |
3038 | | |
3039 | | /* Copy arguments: R[0] = reserved, R[1..n] = args */ |
3040 | 3.08k | new_regs[0] = value_unit(); /* Reserved slot */ |
3041 | 4.18k | for (int i = 0; i < b; i++) { |
3042 | 1.09k | value_free(&new_regs[1 + i]); |
3043 | 1.09k | new_regs[1 + i] = rvm_clone(&R[a + 1 + i]); |
3044 | 1.09k | } |
3045 | | |
3046 | | /* Set up upvalues */ |
3047 | 3.08k | ObjUpvalue **upvals = (ObjUpvalue **)func->as.closure.captured_env; |
3048 | 3.08k | size_t uv_count = func->region_id != (size_t)-1 ? func->region_id : 0; |
3049 | | |
3050 | | /* Push new frame */ |
3051 | 3.08k | RegCallFrame *new_frame = &vm->frames[vm->frame_count++]; |
3052 | 3.08k | new_frame->chunk = fn_chunk; |
3053 | 3.08k | new_frame->ip = fn_chunk->code; |
3054 | 3.08k | new_frame->regs = new_regs; |
3055 | 3.08k | new_frame->reg_count = REGVM_REG_MAX; |
3056 | 3.08k | new_frame->upvalues = upvals; |
3057 | 3.08k | new_frame->upvalue_count = uv_count; |
3058 | 3.08k | new_frame->caller_result_reg = a; /* RETURN puts result here */ |
3059 | 3.08k | frame = new_frame; |
3060 | 3.08k | R = new_regs; |
3061 | 3.08k | DISPATCH(); |
3062 | 3.08k | } |
3063 | | |
3064 | 2.79k | CASE(RETURN) { |
3065 | 2.79k | uint8_t a = REG_GET_A(instr); |
3066 | 2.79k | uint8_t b = REG_GET_B(instr); |
3067 | | |
3068 | 2.79k | LatValue ret_val = (b > 0) ? rvm_clone(&R[a]) : value_unit(); |
3069 | 2.79k | uint8_t dest_reg = frame->caller_result_reg; |
3070 | | |
3071 | | /* Close any open upvalues that point into this frame's registers */ |
3072 | 2.79k | { |
3073 | 2.79k | LatValue *frame_base = frame->regs; |
3074 | 2.79k | LatValue *frame_end = frame_base + REGVM_REG_MAX; |
3075 | 2.79k | ObjUpvalue **prev = &vm->open_upvalues; |
3076 | 2.94k | while (*prev) { |
3077 | 151 | ObjUpvalue *uv = *prev; |
3078 | 151 | if (uv->location >= frame_base && uv->location < frame_end) { |
3079 | | /* Close this upvalue: move value to uv->closed */ |
3080 | 52 | uv->closed = *uv->location; |
3081 | 52 | *uv->location = value_nil(); /* prevent double-free */ |
3082 | 52 | uv->location = &uv->closed; |
3083 | 52 | *prev = uv->next; |
3084 | 99 | } else { |
3085 | 99 | prev = &uv->next; |
3086 | 99 | } |
3087 | 151 | } |
3088 | 2.79k | } |
3089 | | |
3090 | | /* Clean up current frame's registers */ |
3091 | 719k | for (int i = 0; i < REGVM_REG_MAX; i++) |
3092 | 716k | value_free_inline(&frame->regs[i]); |
3093 | | |
3094 | 2.79k | vm->frame_count--; |
3095 | 2.79k | vm->reg_stack_top -= REGVM_REG_MAX; |
3096 | | |
3097 | 2.79k | if (vm->frame_count == base_frame) { |
3098 | | /* Return to caller (or top-level) */ |
3099 | 1.14k | *result = ret_val; |
3100 | 1.14k | return REGVM_OK; |
3101 | 1.14k | } |
3102 | | |
3103 | | /* Restore caller frame */ |
3104 | 1.65k | frame = &vm->frames[vm->frame_count - 1]; |
3105 | 1.65k | R = frame->regs; |
3106 | | |
3107 | 1.65k | reg_set(&R[dest_reg], ret_val); |
3108 | 1.65k | DISPATCH(); |
3109 | 1.65k | } |
3110 | | |
3111 | 2.78k | CASE(CLOSURE) { |
3112 | 2.78k | uint8_t a = REG_GET_A(instr); |
3113 | 2.78k | uint16_t bx = REG_GET_Bx(instr); |
3114 | 2.78k | LatValue fn_proto = frame->chunk->constants[bx]; |
3115 | | |
3116 | | /* Create closure from prototype */ |
3117 | 2.78k | LatValue closure; |
3118 | 2.78k | memset(&closure, 0, sizeof(closure)); |
3119 | 2.78k | closure.type = VAL_CLOSURE; |
3120 | 2.78k | closure.phase = VTAG_UNPHASED; |
3121 | 2.78k | closure.region_id = (size_t)-1; |
3122 | 2.78k | closure.as.closure.body = NULL; |
3123 | 2.78k | closure.as.closure.native_fn = fn_proto.as.closure.native_fn; |
3124 | 2.78k | closure.as.closure.param_count = fn_proto.as.closure.param_count; |
3125 | | /* Copy param_names from prototype */ |
3126 | 2.78k | if (fn_proto.as.closure.param_names && fn_proto.as.closure.param_count > 0) { |
3127 | 1.83k | closure.as.closure.param_names = malloc(fn_proto.as.closure.param_count * sizeof(char *)); |
3128 | 4.86k | for (size_t pi = 0; pi < fn_proto.as.closure.param_count; pi++) |
3129 | 3.02k | closure.as.closure.param_names[pi] = fn_proto.as.closure.param_names[pi] |
3130 | 3.02k | ? strdup(fn_proto.as.closure.param_names[pi]) : NULL; |
3131 | 1.83k | } else { |
3132 | 944 | closure.as.closure.param_names = NULL; |
3133 | 944 | } |
3134 | 2.78k | closure.as.closure.default_values = NULL; |
3135 | 2.78k | closure.as.closure.has_variadic = fn_proto.as.closure.has_variadic; |
3136 | 2.78k | closure.as.closure.captured_env = NULL; |
3137 | | |
3138 | | /* Process upvalue descriptors that follow the CLOSURE instruction */ |
3139 | | /* Each upvalue descriptor is encoded as a MOVE instruction: |
3140 | | * A=1 means local, A=0 means upvalue; B=index */ |
3141 | 2.78k | size_t uv_count = fn_proto.region_id; /* upvalue count stored by compiler */ |
3142 | 2.78k | ObjUpvalue **upvals = NULL; |
3143 | | |
3144 | 2.78k | if (uv_count > 0) { |
3145 | 32 | upvals = malloc(uv_count * sizeof(ObjUpvalue *)); |
3146 | 84 | for (size_t i = 0; i < uv_count; i++) { |
3147 | 52 | RegInstr desc = READ_INSTR(); |
3148 | 52 | uint8_t is_local = REG_GET_A(desc); |
3149 | 52 | uint8_t index = REG_GET_B(desc); |
3150 | | |
3151 | 52 | if (is_local) { |
3152 | | /* Capture from current frame's register */ |
3153 | 52 | ObjUpvalue *uv = malloc(sizeof(ObjUpvalue)); |
3154 | 52 | uv->location = &R[index]; |
3155 | 52 | uv->closed = value_nil(); |
3156 | 52 | uv->next = vm->open_upvalues; |
3157 | 52 | vm->open_upvalues = uv; |
3158 | 52 | upvals[i] = uv; |
3159 | 52 | } else { |
3160 | | /* Capture from enclosing upvalue */ |
3161 | 0 | if (frame->upvalues && index < frame->upvalue_count) |
3162 | 0 | upvals[i] = frame->upvalues[index]; |
3163 | 0 | else |
3164 | 0 | upvals[i] = NULL; |
3165 | 0 | } |
3166 | 52 | } |
3167 | 32 | closure.as.closure.captured_env = (Env *)upvals; |
3168 | 32 | closure.region_id = uv_count; |
3169 | 32 | } |
3170 | | |
3171 | 2.78k | reg_set(&R[a], closure); |
3172 | 2.78k | DISPATCH(); |
3173 | 2.78k | } |
3174 | | |
3175 | 2.78k | CASE(NEWARRAY) { |
3176 | 256 | uint8_t a = REG_GET_A(instr); |
3177 | 256 | uint8_t b = REG_GET_B(instr); /* base register */ |
3178 | 256 | uint8_t c = REG_GET_C(instr); /* count */ |
3179 | | |
3180 | 256 | if (c == 0) { |
3181 | 61 | reg_set(&R[a], value_array(NULL, 0)); |
3182 | 195 | } else { |
3183 | 195 | LatValue *elems = malloc(c * sizeof(LatValue)); |
3184 | 733 | for (int i = 0; i < c; i++) |
3185 | 538 | elems[i] = rvm_clone(&R[b + i]); |
3186 | 195 | reg_set(&R[a], value_array(elems, c)); |
3187 | 195 | free(elems); |
3188 | 195 | } |
3189 | 256 | DISPATCH(); |
3190 | 256 | } |
3191 | | |
3192 | 256 | CASE(NEWSTRUCT) { |
3193 | 194 | uint8_t a = REG_GET_A(instr); |
3194 | | /* b is unused; name constant comes from follow-up LOADK instruction */ |
3195 | 194 | uint8_t c = REG_GET_C(instr); /* field count */ |
3196 | | |
3197 | | /* Read the follow-up LOADK instruction to get the full constant index */ |
3198 | 194 | RegInstr name_instr = READ_INSTR(); |
3199 | 194 | uint16_t name_ki = REG_GET_Bx(name_instr); |
3200 | 194 | const char *struct_name = frame->chunk->constants[name_ki].as.str_val; |
3201 | | |
3202 | | /* Look up field names from struct metadata */ |
3203 | 194 | char meta_name[256]; |
3204 | 194 | snprintf(meta_name, sizeof(meta_name), "__struct_%s", struct_name); |
3205 | 194 | LatValue meta; |
3206 | 194 | if (!env_get(vm->env, meta_name, &meta)) { |
3207 | 0 | RVM_ERROR("unknown struct '%s'", struct_name); |
3208 | 0 | } |
3209 | | |
3210 | 194 | if (meta.type != VAL_ARRAY || (int)meta.as.array.len != c) { |
3211 | 0 | RVM_ERROR("struct '%s' field count mismatch", struct_name); |
3212 | 0 | } |
3213 | | |
3214 | | /* Build field names array */ |
3215 | 194 | char **field_names = malloc(c * sizeof(char *)); |
3216 | 194 | LatValue *field_values = malloc(c * sizeof(LatValue)); |
3217 | 578 | for (int i = 0; i < c; i++) { |
3218 | 384 | field_names[i] = strdup(meta.as.array.elems[i].as.str_val); |
3219 | | /* Field values are in registers a+1..a+c */ |
3220 | | /* Actually, the compiler puts them starting at base (which is free'd by the compiler, |
3221 | | * but the values are still in registers we can read) */ |
3222 | | /* The register where fields were compiled: they're before `a` in the temp window. |
3223 | | * But we don't know the exact base. Let's use the meta field names to find them |
3224 | | * from the struct literal compilation. Actually, let me revisit the compiler... */ |
3225 | | /* The compiler compiles fields into base..base+c-1, then emits NEWSTRUCT into dst(a). |
3226 | | * So fields are in R[a+1..a+c] if base = a+1... But the compiler uses alloc_reg |
3227 | | * for base, so base = next_reg. The fields are contiguous starting at base. |
3228 | | * After NEWSTRUCT, the compiler calls free_regs_to(base). But we haven't freed yet. |
3229 | | * Actually, base was allocated, then fields at base..base+c-1. |
3230 | | * After NEWSTRUCT is emitted, the LOADK follow-up uses base (which was the first alloc'd reg). |
3231 | | * So the fields are at the register given by REG_GET_A(name_instr) and following. */ |
3232 | | /* Wait, looking at the compiler: base = alloc_reg(), fields compiled into base..base+c-1. |
3233 | | * Then NEWSTRUCT A=dst, then LOADK A=base. So base is in REG_GET_A(name_instr). */ |
3234 | 384 | } |
3235 | 194 | uint8_t field_base = REG_GET_A(name_instr); |
3236 | 578 | for (int i = 0; i < c; i++) { |
3237 | 384 | field_values[i] = rvm_clone(&R[field_base + i]); |
3238 | 384 | } |
3239 | | |
3240 | 194 | LatValue strct = value_struct(struct_name, field_names, field_values, c); |
3241 | 578 | for (int i = 0; i < c; i++) |
3242 | 384 | free(field_names[i]); |
3243 | 194 | free(field_names); |
3244 | 194 | free(field_values); |
3245 | | |
3246 | | /* Alloy enforcement: apply per-field phase from struct declaration */ |
3247 | 194 | { |
3248 | 194 | char phase_key[256]; |
3249 | 194 | snprintf(phase_key, sizeof(phase_key), "__struct_phases_%s", struct_name); |
3250 | 194 | LatValue *phase_ref = env_get_ref(vm->env, phase_key); |
3251 | 194 | if (phase_ref && |
3252 | 194 | phase_ref->type == VAL_ARRAY && (int)phase_ref->as.array.len == c) { |
3253 | 3 | strct.as.strct.field_phases = calloc(c, sizeof(PhaseTag)); |
3254 | 9 | for (int i = 0; i < c; i++) { |
3255 | 6 | int64_t p = phase_ref->as.array.elems[i].as.int_val; |
3256 | 6 | if (p == 1) { /* PHASE_CRYSTAL */ |
3257 | 3 | strct.as.strct.field_values[i] = value_freeze(strct.as.strct.field_values[i]); |
3258 | 3 | strct.as.strct.field_phases[i] = VTAG_CRYSTAL; |
3259 | 3 | } else if (p == 0) { /* PHASE_FLUID */ |
3260 | 3 | strct.as.strct.field_phases[i] = VTAG_FLUID; |
3261 | 3 | } else { |
3262 | 0 | strct.as.strct.field_phases[i] = strct.phase; |
3263 | 0 | } |
3264 | 6 | } |
3265 | 3 | } |
3266 | 194 | } |
3267 | | |
3268 | 194 | reg_set(&R[a], strct); |
3269 | 194 | DISPATCH(); |
3270 | 194 | } |
3271 | | |
3272 | 13 | CASE(BUILDRANGE) { |
3273 | 13 | uint8_t a = REG_GET_A(instr); |
3274 | 13 | uint8_t b = REG_GET_B(instr); |
3275 | 13 | uint8_t c = REG_GET_C(instr); |
3276 | 13 | if (R[b].type != VAL_INT || R[c].type != VAL_INT) |
3277 | 0 | RVM_ERROR("range bounds must be integers"); |
3278 | 13 | reg_set(&R[a], value_range(R[b].as.int_val, R[c].as.int_val)); |
3279 | 13 | DISPATCH(); |
3280 | 13 | } |
3281 | | |
3282 | 356 | CASE(LEN) { |
3283 | 356 | uint8_t a = REG_GET_A(instr); |
3284 | 356 | uint8_t b = REG_GET_B(instr); |
3285 | 356 | if (R[b].type == VAL_ARRAY) { |
3286 | 119 | reg_set(&R[a], value_int((int64_t)R[b].as.array.len)); |
3287 | 237 | } else if (R[b].type == VAL_STR) { |
3288 | 0 | reg_set(&R[a], value_int((int64_t)strlen(R[b].as.str_val))); |
3289 | 237 | } else if (R[b].type == VAL_RANGE) { |
3290 | 237 | int64_t len = R[b].as.range.end - R[b].as.range.start; |
3291 | 237 | if (len < 0) len = 0; |
3292 | 237 | reg_set(&R[a], value_int(len)); |
3293 | 237 | } else if (R[b].type == VAL_MAP) { |
3294 | 0 | reg_set(&R[a], value_int((int64_t)lat_map_len(R[b].as.map.map))); |
3295 | 0 | } else if (R[b].type == VAL_SET) { |
3296 | 0 | reg_set(&R[a], value_int((int64_t)lat_map_len(R[b].as.set.map))); |
3297 | 0 | } else if (R[b].type == VAL_TUPLE) { |
3298 | 0 | reg_set(&R[a], value_int((int64_t)R[b].as.tuple.len)); |
3299 | 0 | } else if (R[b].type == VAL_BUFFER) { |
3300 | 0 | reg_set(&R[a], value_int((int64_t)R[b].as.buffer.len)); |
3301 | 0 | } else { |
3302 | 0 | RVM_ERROR("cannot get length of %s", value_type_name(&R[b])); |
3303 | 0 | } |
3304 | 356 | DISPATCH(); |
3305 | 356 | } |
3306 | | |
3307 | 999 | CASE(PRINT) { |
3308 | 999 | uint8_t a = REG_GET_A(instr); |
3309 | 999 | uint8_t b = REG_GET_B(instr); /* count */ |
3310 | 2.00k | for (int i = 0; i < b; i++) { |
3311 | 1.00k | if (i > 0) printf(" "); |
3312 | 1.00k | value_print(&R[a + i], stdout); |
3313 | 1.00k | } |
3314 | 999 | printf("\n"); |
3315 | 999 | DISPATCH(); |
3316 | 999 | } |
3317 | | |
3318 | 2.60k | CASE(INVOKE) { |
3319 | | /* Two-instruction sequence: |
3320 | | * INVOKE A=dst, B=method_ki, C=argc |
3321 | | * data: A=obj_reg, B=args_base, C=0 |
3322 | | * Object is mutated in-place at R[obj_reg] (for push/pop). |
3323 | | * Return value goes into R[dst]. */ |
3324 | 2.60k | uint8_t dst = REG_GET_A(instr); |
3325 | 2.60k | uint8_t method_ki = REG_GET_B(instr); |
3326 | 2.60k | uint8_t argc = REG_GET_C(instr); |
3327 | | |
3328 | | /* Read data word */ |
3329 | 2.60k | RegInstr data = *frame->ip++; |
3330 | 2.60k | uint8_t obj_reg = REG_GET_A(data); |
3331 | 2.60k | uint8_t args_base = REG_GET_B(data); |
3332 | | |
3333 | 2.60k | const char *method_name = frame->chunk->constants[method_ki].as.str_val; |
3334 | | |
3335 | | /* Try builtin */ |
3336 | 2.60k | LatValue invoke_result; |
3337 | 2.60k | LatValue *invoke_args = (argc > 0) ? &R[args_base] : NULL; |
3338 | 2.60k | if (rvm_invoke_builtin(vm, &R[obj_reg], method_name, invoke_args, argc, &invoke_result)) { |
3339 | 2.59k | if (vm->error) |
3340 | 9 | return REGVM_RUNTIME_ERROR; |
3341 | | /* Object was mutated in-place at R[obj_reg]; result goes to R[dst] */ |
3342 | 2.58k | reg_set(&R[dst], invoke_result); |
3343 | 2.58k | DISPATCH(); |
3344 | 2.58k | } |
3345 | | |
3346 | | /* Check for callable closure field in map */ |
3347 | 2.59k | if (R[obj_reg].type == VAL_MAP) { |
3348 | 0 | LatValue *field = lat_map_get(R[obj_reg].as.map.map, method_name); |
3349 | 0 | if (field && field->type == VAL_CLOSURE) { |
3350 | | /* Native C function in map */ |
3351 | 0 | if (field->as.closure.default_values == VM_NATIVE_MARKER) { |
3352 | 0 | VMNativeFn native = (VMNativeFn)field->as.closure.native_fn; |
3353 | 0 | LatValue *call_args = (argc > 0) ? &R[args_base] : NULL; |
3354 | 0 | LatValue ret = native(call_args, argc); |
3355 | 0 | if (vm->rt->error) { |
3356 | 0 | vm->error = vm->rt->error; |
3357 | 0 | vm->rt->error = NULL; |
3358 | 0 | value_free(&ret); |
3359 | 0 | return REGVM_RUNTIME_ERROR; |
3360 | 0 | } |
3361 | 0 | reg_set(&R[dst], ret); |
3362 | 0 | DISPATCH(); |
3363 | 0 | } |
3364 | | /* Extension native function in map */ |
3365 | 0 | if (field->as.closure.default_values == VM_EXT_MARKER) { |
3366 | 0 | LatValue *call_args = (argc > 0) ? &R[args_base] : NULL; |
3367 | 0 | LatValue ret = ext_call_native(field->as.closure.native_fn, |
3368 | 0 | call_args, (size_t)argc); |
3369 | 0 | if (ret.type == VAL_STR && ret.as.str_val && |
3370 | 0 | strncmp(ret.as.str_val, "EVAL_ERROR:", 11) == 0) { |
3371 | 0 | vm->error = strdup(ret.as.str_val + 11); |
3372 | 0 | value_free(&ret); |
3373 | 0 | return REGVM_RUNTIME_ERROR; |
3374 | 0 | } |
3375 | 0 | reg_set(&R[dst], ret); |
3376 | 0 | DISPATCH(); |
3377 | 0 | } |
3378 | | /* RegChunk closure in map */ |
3379 | 0 | RegChunk *fn_chunk = (RegChunk *)field->as.closure.native_fn; |
3380 | 0 | if (fn_chunk && fn_chunk->magic == REGCHUNK_MAGIC) { |
3381 | 0 | if (vm->frame_count >= REGVM_FRAMES_MAX) |
3382 | 0 | RVM_ERROR("call stack overflow"); |
3383 | | |
3384 | 0 | size_t new_base = vm->reg_stack_top; |
3385 | 0 | LatValue *new_regs = &vm->reg_stack[new_base]; |
3386 | 0 | vm->reg_stack_top += REGVM_REG_MAX; |
3387 | 0 | for (int i = 0; i < REGVM_REG_MAX; i++) |
3388 | 0 | new_regs[i] = value_nil(); |
3389 | | |
3390 | | /* Slot 0 = reserved, slots 1+ = args (no self for map closures) */ |
3391 | 0 | new_regs[0] = value_unit(); |
3392 | 0 | for (int i = 0; i < argc; i++) { |
3393 | 0 | value_free(&new_regs[1 + i]); |
3394 | 0 | new_regs[1 + i] = rvm_clone(&R[args_base + i]); |
3395 | 0 | } |
3396 | |
|
3397 | 0 | ObjUpvalue **upvals = (ObjUpvalue **)field->as.closure.captured_env; |
3398 | 0 | size_t uv_count = field->region_id != (size_t)-1 ? field->region_id : 0; |
3399 | |
|
3400 | 0 | RegCallFrame *new_frame = &vm->frames[vm->frame_count++]; |
3401 | 0 | new_frame->chunk = fn_chunk; |
3402 | 0 | new_frame->ip = fn_chunk->code; |
3403 | 0 | new_frame->regs = new_regs; |
3404 | 0 | new_frame->reg_count = REGVM_REG_MAX; |
3405 | 0 | new_frame->upvalues = upvals; |
3406 | 0 | new_frame->upvalue_count = uv_count; |
3407 | 0 | new_frame->caller_result_reg = dst; |
3408 | 0 | frame = new_frame; |
3409 | 0 | R = new_regs; |
3410 | 0 | DISPATCH(); |
3411 | 0 | } |
3412 | | /* Stack-VM closure in map — use regvm bridge */ |
3413 | 0 | if (field->as.closure.native_fn) { |
3414 | 0 | LatValue *call_args = (argc > 0) ? &R[args_base] : NULL; |
3415 | 0 | LatValue ret = regvm_call_closure(vm, field, call_args, argc); |
3416 | 0 | if (vm->error) return REGVM_RUNTIME_ERROR; |
3417 | 0 | reg_set(&R[dst], ret); |
3418 | 0 | DISPATCH(); |
3419 | 0 | } |
3420 | 0 | } |
3421 | 0 | } |
3422 | | |
3423 | | /* Check for callable closure field in struct */ |
3424 | 2.59k | if (R[obj_reg].type == VAL_STRUCT) { |
3425 | 24 | for (size_t fi = 0; fi < R[obj_reg].as.strct.field_count; fi++) { |
3426 | 14 | if (strcmp(R[obj_reg].as.strct.field_names[fi], method_name) != 0) continue; |
3427 | 4 | LatValue *field = &R[obj_reg].as.strct.field_values[fi]; |
3428 | 4 | if (field->type == VAL_CLOSURE && field->as.closure.native_fn) { |
3429 | 4 | RegChunk *fn_chunk = (RegChunk *)field->as.closure.native_fn; |
3430 | 4 | if (vm->frame_count >= REGVM_FRAMES_MAX) |
3431 | 0 | RVM_ERROR("call stack overflow"); |
3432 | | |
3433 | 4 | size_t new_base = vm->reg_stack_top; |
3434 | 4 | LatValue *new_regs = &vm->reg_stack[new_base]; |
3435 | 4 | vm->reg_stack_top += REGVM_REG_MAX; |
3436 | 1.02k | for (int i = 0; i < REGVM_REG_MAX; i++) |
3437 | 1.02k | new_regs[i] = value_nil(); |
3438 | | |
3439 | | /* Slot 0 = reserved, slot 1 = self, slots 2+ = args */ |
3440 | 4 | new_regs[0] = value_unit(); |
3441 | 4 | new_regs[1] = rvm_clone(&R[obj_reg]); /* self = first param */ |
3442 | 6 | for (int i = 0; i < argc; i++) { |
3443 | 2 | value_free(&new_regs[2 + i]); |
3444 | 2 | new_regs[2 + i] = rvm_clone(&R[args_base + i]); |
3445 | 2 | } |
3446 | | |
3447 | 4 | ObjUpvalue **upvals = (ObjUpvalue **)field->as.closure.captured_env; |
3448 | 4 | size_t uv_count = field->region_id != (size_t)-1 ? field->region_id : 0; |
3449 | | |
3450 | 4 | RegCallFrame *new_frame = &vm->frames[vm->frame_count++]; |
3451 | 4 | new_frame->chunk = fn_chunk; |
3452 | 4 | new_frame->ip = fn_chunk->code; |
3453 | 4 | new_frame->regs = new_regs; |
3454 | 4 | new_frame->reg_count = REGVM_REG_MAX; |
3455 | 4 | new_frame->upvalues = upvals; |
3456 | 4 | new_frame->upvalue_count = uv_count; |
3457 | 4 | new_frame->caller_result_reg = dst; |
3458 | 4 | frame = new_frame; |
3459 | 4 | R = new_regs; |
3460 | 4 | DISPATCH(); |
3461 | 4 | } |
3462 | 4 | } |
3463 | 10 | } |
3464 | | |
3465 | | /* Check for impl method (TypeName::method) */ |
3466 | 2.59k | if (R[obj_reg].type == VAL_STRUCT) { |
3467 | 6 | char key[256]; |
3468 | 6 | snprintf(key, sizeof(key), "%s::%s", R[obj_reg].as.strct.name, method_name); |
3469 | 6 | LatValue impl_fn; |
3470 | 6 | if (env_get(vm->env, key, &impl_fn) && impl_fn.type == VAL_CLOSURE) { |
3471 | 6 | RegChunk *fn_chunk = (RegChunk *)impl_fn.as.closure.native_fn; |
3472 | 6 | if (!fn_chunk) goto invoke_fail; |
3473 | | |
3474 | 6 | if (vm->frame_count >= REGVM_FRAMES_MAX) |
3475 | 0 | RVM_ERROR("call stack overflow"); |
3476 | | |
3477 | 6 | size_t new_base = vm->reg_stack_top; |
3478 | 6 | LatValue *new_regs = &vm->reg_stack[new_base]; |
3479 | 6 | vm->reg_stack_top += REGVM_REG_MAX; |
3480 | 1.54k | for (int i = 0; i < REGVM_REG_MAX; i++) |
3481 | 1.53k | new_regs[i] = value_nil(); |
3482 | | |
3483 | | /* ITEM_IMPL compiles self at slot 0, other params at slot 1+ */ |
3484 | 6 | new_regs[0] = rvm_clone(&R[obj_reg]); /* self */ |
3485 | 7 | for (int i = 0; i < argc; i++) { |
3486 | 1 | value_free(&new_regs[1 + i]); |
3487 | 1 | new_regs[1 + i] = rvm_clone(&R[args_base + i]); |
3488 | 1 | } |
3489 | | |
3490 | 6 | ObjUpvalue **upvals = (ObjUpvalue **)impl_fn.as.closure.captured_env; |
3491 | 6 | size_t uv_count = impl_fn.region_id != (size_t)-1 ? impl_fn.region_id : 0; |
3492 | | |
3493 | 6 | RegCallFrame *new_frame = &vm->frames[vm->frame_count++]; |
3494 | 6 | new_frame->chunk = fn_chunk; |
3495 | 6 | new_frame->ip = fn_chunk->code; |
3496 | 6 | new_frame->regs = new_regs; |
3497 | 6 | new_frame->reg_count = REGVM_REG_MAX; |
3498 | 6 | new_frame->upvalues = upvals; |
3499 | 6 | new_frame->upvalue_count = uv_count; |
3500 | 6 | new_frame->caller_result_reg = dst; |
3501 | 6 | frame = new_frame; |
3502 | 6 | R = new_regs; |
3503 | 6 | DISPATCH(); |
3504 | 6 | } |
3505 | 6 | } |
3506 | | |
3507 | 2.59k | invoke_fail: |
3508 | 1 | RVM_ERROR("no method '%s' on %s", method_name, value_type_name(&R[obj_reg])); |
3509 | 1 | } |
3510 | | |
3511 | 44 | CASE(FREEZE) { |
3512 | 44 | uint8_t a = REG_GET_A(instr); |
3513 | 44 | uint8_t b = REG_GET_B(instr); |
3514 | 44 | if (R[b].type == VAL_CHANNEL) |
3515 | 0 | RVM_ERROR("cannot freeze a channel"); |
3516 | 44 | LatValue frozen = value_freeze(rvm_clone(&R[b])); |
3517 | 44 | reg_set(&R[a], frozen); |
3518 | 44 | DISPATCH(); |
3519 | 44 | } |
3520 | | |
3521 | 6 | CASE(THAW) { |
3522 | 6 | uint8_t a = REG_GET_A(instr); |
3523 | 6 | uint8_t b = REG_GET_B(instr); |
3524 | 6 | LatValue thawed = value_thaw(&R[b]); |
3525 | 6 | reg_set(&R[a], thawed); |
3526 | 6 | DISPATCH(); |
3527 | 6 | } |
3528 | | |
3529 | 6 | CASE(CLONE) { |
3530 | 3 | uint8_t a = REG_GET_A(instr); |
3531 | 3 | uint8_t b = REG_GET_B(instr); |
3532 | 3 | reg_set(&R[a], value_deep_clone(&R[b])); |
3533 | 3 | DISPATCH(); |
3534 | 3 | } |
3535 | | |
3536 | 47 | CASE(ITERINIT) { |
3537 | | /* A = destination (collection stays in A), B = source */ |
3538 | 47 | uint8_t a = REG_GET_A(instr); |
3539 | 47 | uint8_t b = REG_GET_B(instr); |
3540 | 47 | if (R[b].type == VAL_MAP) { |
3541 | | /* Convert map to array of [key, value] pairs for uniform iteration */ |
3542 | 1 | LatMap *m = R[b].as.map.map; |
3543 | 1 | size_t cap = m->cap; |
3544 | 1 | size_t count = lat_map_len(m); |
3545 | 1 | LatValue *entries = malloc((count > 0 ? count : 1) * sizeof(LatValue)); |
3546 | 1 | size_t idx = 0; |
3547 | 17 | for (size_t i = 0; i < cap; i++) { |
3548 | 16 | if (m->entries[i].state != MAP_OCCUPIED) continue; |
3549 | 1 | LatValue pair[2]; |
3550 | 1 | pair[0] = value_string(m->entries[i].key); |
3551 | 1 | pair[1] = rvm_clone((LatValue *)m->entries[i].value); |
3552 | 1 | entries[idx++] = value_array(pair, 2); |
3553 | 1 | } |
3554 | 1 | reg_set(&R[a], value_array(entries, idx)); |
3555 | 1 | free(entries); |
3556 | 46 | } else if (R[b].type == VAL_SET) { |
3557 | | /* Convert set to array of values for uniform iteration */ |
3558 | 1 | LatMap *m = R[b].as.set.map; |
3559 | 1 | size_t cap = m->cap; |
3560 | 1 | size_t count = lat_map_len(m); |
3561 | 1 | LatValue *elems = malloc((count > 0 ? count : 1) * sizeof(LatValue)); |
3562 | 1 | size_t idx = 0; |
3563 | 17 | for (size_t i = 0; i < cap; i++) { |
3564 | 16 | if (m->entries[i].state != MAP_OCCUPIED) continue; |
3565 | 1 | elems[idx++] = rvm_clone((LatValue *)m->entries[i].value); |
3566 | 1 | } |
3567 | 1 | reg_set(&R[a], value_array(elems, idx)); |
3568 | 1 | free(elems); |
3569 | 45 | } else if (R[b].type == VAL_STR) { |
3570 | | /* Convert string to array of characters for uniform iteration */ |
3571 | 0 | size_t len = strlen(R[b].as.str_val); |
3572 | 0 | LatValue *chars = malloc((len > 0 ? len : 1) * sizeof(LatValue)); |
3573 | 0 | for (size_t i = 0; i < len; i++) { |
3574 | 0 | char ch[2] = { R[b].as.str_val[i], '\0' }; |
3575 | 0 | chars[i] = value_string(ch); |
3576 | 0 | } |
3577 | 0 | reg_set(&R[a], value_array(chars, len)); |
3578 | 0 | free(chars); |
3579 | 45 | } else { |
3580 | 45 | if (a != b) |
3581 | 0 | reg_set(&R[a], rvm_clone(&R[b])); |
3582 | 45 | } |
3583 | | /* The collection stays in R[a]. Index starts at 0 (set by compiler). */ |
3584 | 47 | DISPATCH(); |
3585 | 47 | } |
3586 | | |
3587 | 306 | CASE(ITERNEXT) { |
3588 | | /* A = result (loop var), B = collection, C = index register */ |
3589 | 306 | uint8_t a = REG_GET_A(instr); |
3590 | 306 | uint8_t b = REG_GET_B(instr); |
3591 | 306 | uint8_t c = REG_GET_C(instr); |
3592 | | |
3593 | 306 | if (R[b].type == VAL_RANGE) { |
3594 | 229 | int64_t idx = R[c].as.int_val; |
3595 | 229 | int64_t start = R[b].as.range.start; |
3596 | 229 | int64_t end = R[b].as.range.end; |
3597 | 229 | int64_t current_val = start + idx; |
3598 | 229 | if (current_val >= end) { |
3599 | | /* Done — set result to nil (falsy, triggers JMPFALSE) */ |
3600 | 0 | reg_set(&R[a], value_nil()); |
3601 | 229 | } else { |
3602 | 229 | reg_set(&R[a], value_int(current_val)); |
3603 | 229 | } |
3604 | 229 | } else if (R[b].type == VAL_ARRAY) { |
3605 | 77 | int64_t idx = R[c].as.int_val; |
3606 | 77 | if ((size_t)idx >= R[b].as.array.len) { |
3607 | 0 | reg_set(&R[a], value_nil()); |
3608 | 77 | } else { |
3609 | 77 | reg_set(&R[a], rvm_clone(&R[b].as.array.elems[idx])); |
3610 | 77 | } |
3611 | 77 | } else { |
3612 | 0 | RVM_ERROR("cannot iterate over %s", value_type_name(&R[b])); |
3613 | 0 | } |
3614 | 306 | DISPATCH(); |
3615 | 306 | } |
3616 | | |
3617 | 343 | CASE(MARKFLUID) { |
3618 | 343 | uint8_t a = REG_GET_A(instr); |
3619 | 343 | R[a].phase = VTAG_FLUID; |
3620 | 343 | DISPATCH(); |
3621 | 343 | } |
3622 | | |
3623 | | /* ── Bitwise operations ── */ |
3624 | | |
3625 | 343 | CASE(BIT_AND) { |
3626 | 5 | uint8_t a = REG_GET_A(instr); |
3627 | 5 | uint8_t b = REG_GET_B(instr); |
3628 | 5 | uint8_t c = REG_GET_C(instr); |
3629 | 5 | if (R[b].type != VAL_INT || R[c].type != VAL_INT) |
3630 | 0 | RVM_ERROR("bitwise AND requires integers"); |
3631 | 5 | reg_set(&R[a], value_int(R[b].as.int_val & R[c].as.int_val)); |
3632 | 5 | DISPATCH(); |
3633 | 5 | } |
3634 | | |
3635 | 4 | CASE(BIT_OR) { |
3636 | 4 | uint8_t a = REG_GET_A(instr); |
3637 | 4 | uint8_t b = REG_GET_B(instr); |
3638 | 4 | uint8_t c = REG_GET_C(instr); |
3639 | 4 | if (R[b].type != VAL_INT || R[c].type != VAL_INT) |
3640 | 0 | RVM_ERROR("bitwise OR requires integers"); |
3641 | 4 | reg_set(&R[a], value_int(R[b].as.int_val | R[c].as.int_val)); |
3642 | 4 | DISPATCH(); |
3643 | 4 | } |
3644 | | |
3645 | 4 | CASE(BIT_XOR) { |
3646 | 4 | uint8_t a = REG_GET_A(instr); |
3647 | 4 | uint8_t b = REG_GET_B(instr); |
3648 | 4 | uint8_t c = REG_GET_C(instr); |
3649 | 4 | if (R[b].type != VAL_INT || R[c].type != VAL_INT) |
3650 | 0 | RVM_ERROR("bitwise XOR requires integers"); |
3651 | 4 | reg_set(&R[a], value_int(R[b].as.int_val ^ R[c].as.int_val)); |
3652 | 4 | DISPATCH(); |
3653 | 4 | } |
3654 | | |
3655 | 3 | CASE(BIT_NOT) { |
3656 | 3 | uint8_t a = REG_GET_A(instr); |
3657 | 3 | uint8_t b = REG_GET_B(instr); |
3658 | 3 | if (R[b].type != VAL_INT) |
3659 | 0 | RVM_ERROR("bitwise NOT requires integer"); |
3660 | 3 | reg_set(&R[a], value_int(~R[b].as.int_val)); |
3661 | 3 | DISPATCH(); |
3662 | 3 | } |
3663 | | |
3664 | 3 | CASE(LSHIFT) { |
3665 | 3 | uint8_t a = REG_GET_A(instr); |
3666 | 3 | uint8_t b = REG_GET_B(instr); |
3667 | 3 | uint8_t c = REG_GET_C(instr); |
3668 | 3 | if (R[b].type != VAL_INT || R[c].type != VAL_INT) |
3669 | 0 | RVM_ERROR("left shift requires integers"); |
3670 | 3 | if (R[c].as.int_val < 0 || R[c].as.int_val > 63) |
3671 | 1 | RVM_ERROR("shift amount out of range (0..63)"); |
3672 | 3 | reg_set(&R[a], value_int(R[b].as.int_val << R[c].as.int_val)); |
3673 | 3 | DISPATCH(); |
3674 | 3 | } |
3675 | | |
3676 | 2 | CASE(RSHIFT) { |
3677 | 2 | uint8_t a = REG_GET_A(instr); |
3678 | 2 | uint8_t b = REG_GET_B(instr); |
3679 | 2 | uint8_t c = REG_GET_C(instr); |
3680 | 2 | if (R[b].type != VAL_INT || R[c].type != VAL_INT) |
3681 | 0 | RVM_ERROR("right shift requires integers"); |
3682 | 2 | reg_set(&R[a], value_int(R[b].as.int_val >> R[c].as.int_val)); |
3683 | 2 | DISPATCH(); |
3684 | 2 | } |
3685 | | |
3686 | | /* ── Tuple ── */ |
3687 | | |
3688 | 11 | CASE(NEWTUPLE) { |
3689 | 11 | uint8_t a = REG_GET_A(instr); |
3690 | 11 | uint8_t b = REG_GET_B(instr); |
3691 | 11 | uint8_t c = REG_GET_C(instr); /* count */ |
3692 | 11 | LatValue *elems = c > 0 ? malloc(c * sizeof(LatValue)) : NULL; |
3693 | 42 | for (int i = 0; i < c; i++) |
3694 | 31 | elems[i] = rvm_clone(&R[b + i]); |
3695 | 11 | LatValue tup; |
3696 | 11 | tup.type = VAL_TUPLE; |
3697 | 11 | tup.phase = VTAG_CRYSTAL; |
3698 | 11 | tup.region_id = REGION_NONE; |
3699 | 11 | tup.as.tuple.elems = elems; |
3700 | 11 | tup.as.tuple.len = c; |
3701 | 11 | reg_set(&R[a], tup); |
3702 | 11 | DISPATCH(); |
3703 | 11 | } |
3704 | | |
3705 | | /* ── Spread/Flatten ── */ |
3706 | | |
3707 | 15 | CASE(ARRAY_FLATTEN) { |
3708 | 15 | uint8_t a = REG_GET_A(instr); |
3709 | 15 | uint8_t b = REG_GET_B(instr); |
3710 | 15 | if (R[b].type != VAL_ARRAY) { |
3711 | 0 | reg_set(&R[a], rvm_clone(&R[b])); |
3712 | 0 | DISPATCH(); |
3713 | 0 | } |
3714 | | /* One-level flatten */ |
3715 | 15 | size_t cap = R[b].as.array.len * 2; |
3716 | 15 | if (cap == 0) cap = 1; |
3717 | 15 | LatValue *elems = malloc(cap * sizeof(LatValue)); |
3718 | 15 | size_t out = 0; |
3719 | 46 | for (size_t i = 0; i < R[b].as.array.len; i++) { |
3720 | 31 | if (R[b].as.array.elems[i].type == VAL_ARRAY) { |
3721 | 9 | LatValue *inner = &R[b].as.array.elems[i]; |
3722 | 27 | for (size_t j = 0; j < inner->as.array.len; j++) { |
3723 | 18 | if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); } |
3724 | 18 | elems[out++] = rvm_clone(&inner->as.array.elems[j]); |
3725 | 18 | } |
3726 | 22 | } else { |
3727 | 22 | if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); } |
3728 | 22 | elems[out++] = rvm_clone(&R[b].as.array.elems[i]); |
3729 | 22 | } |
3730 | 31 | } |
3731 | 15 | reg_set(&R[a], value_array(elems, out)); |
3732 | 15 | free(elems); |
3733 | 15 | DISPATCH(); |
3734 | 15 | } |
3735 | | |
3736 | | /* ── Enum with payload ── */ |
3737 | | |
3738 | 15 | CASE(NEWENUM) { |
3739 | 4 | uint8_t dst = REG_GET_A(instr); |
3740 | 4 | uint8_t name_ki_lo = REG_GET_B(instr); |
3741 | 4 | uint8_t argc = REG_GET_C(instr); |
3742 | | |
3743 | | /* Read data word: A=base, B=variant_ki, C=name_ki_hi */ |
3744 | 4 | RegInstr data = READ_INSTR(); |
3745 | 4 | uint8_t base = REG_GET_A(data); |
3746 | 4 | uint8_t var_ki = REG_GET_B(data); |
3747 | 4 | uint8_t name_ki_hi = REG_GET_C(data); |
3748 | | |
3749 | 4 | uint16_t name_ki = (uint16_t)name_ki_lo | ((uint16_t)name_ki_hi << 8); |
3750 | 4 | const char *enum_name = frame->chunk->constants[name_ki].as.str_val; |
3751 | 4 | const char *variant_name = frame->chunk->constants[var_ki].as.str_val; |
3752 | | |
3753 | 4 | if (argc == 0) { |
3754 | 0 | reg_set(&R[dst], value_enum(enum_name, variant_name, NULL, 0)); |
3755 | 4 | } else { |
3756 | 4 | LatValue *payload = malloc(argc * sizeof(LatValue)); |
3757 | 10 | for (int i = 0; i < argc; i++) |
3758 | 6 | payload[i] = rvm_clone(&R[base + i]); |
3759 | 4 | reg_set(&R[dst], value_enum(enum_name, variant_name, payload, argc)); |
3760 | 4 | free(payload); |
3761 | 4 | } |
3762 | 4 | DISPATCH(); |
3763 | 4 | } |
3764 | | |
3765 | | /* ── Optional chaining ── */ |
3766 | | |
3767 | 50 | CASE(JMPNOTNIL) { |
3768 | 50 | uint8_t a = REG_GET_A(instr); |
3769 | 50 | int16_t offset = REG_GET_sBx(instr); |
3770 | 50 | if (R[a].type != VAL_NIL) |
3771 | 24 | frame->ip += offset; |
3772 | 50 | DISPATCH(); |
3773 | 50 | } |
3774 | | |
3775 | | /* ── Exception handling ── */ |
3776 | | |
3777 | 50 | CASE(PUSH_HANDLER) { |
3778 | 38 | uint8_t a = REG_GET_A(instr); |
3779 | 38 | int16_t offset = REG_GET_sBx(instr); |
3780 | 38 | if (vm->handler_count >= REGVM_HANDLER_MAX) |
3781 | 0 | RVM_ERROR("exception handler stack overflow"); |
3782 | 38 | RegHandler *h = &vm->handlers[vm->handler_count++]; |
3783 | 38 | h->ip = frame->ip + offset; |
3784 | 38 | h->chunk = frame->chunk; |
3785 | 38 | h->frame_index = (size_t)(vm->frame_count - 1); |
3786 | 38 | h->reg_stack_top = vm->reg_stack_top; |
3787 | 38 | h->error_reg = a; |
3788 | 38 | DISPATCH(); |
3789 | 38 | } |
3790 | | |
3791 | 14 | CASE(POP_HANDLER) { |
3792 | 14 | if (vm->handler_count > 0) |
3793 | 14 | vm->handler_count--; |
3794 | 14 | DISPATCH(); |
3795 | 14 | } |
3796 | | |
3797 | 14 | CASE(THROW) { |
3798 | 10 | uint8_t a = REG_GET_A(instr); |
3799 | 10 | LatValue thrown = rvm_clone(&R[a]); |
3800 | | |
3801 | 10 | if (vm->handler_count == 0) { |
3802 | | /* Match stack VM behavior: string exceptions pass directly, |
3803 | | * non-string exceptions get "unhandled exception:" wrapper. |
3804 | | * No [line N] prefix for thrown exceptions. */ |
3805 | 10 | if (thrown.type == VAL_STR) { |
3806 | 10 | vm->error = strdup(thrown.as.str_val); |
3807 | 10 | } else { |
3808 | 0 | char *repr = value_display(&thrown); |
3809 | 0 | (void)asprintf(&vm->error, "unhandled exception: %s", repr); |
3810 | 0 | free(repr); |
3811 | 0 | } |
3812 | 10 | value_free(&thrown); |
3813 | 10 | return REGVM_RUNTIME_ERROR; |
3814 | 10 | } |
3815 | | |
3816 | | /* Unwind to handler */ |
3817 | 0 | RegHandler h = vm->handlers[--vm->handler_count]; |
3818 | | |
3819 | | /* Clean up frames between current and handler frame */ |
3820 | 0 | while (vm->frame_count - 1 > (int)h.frame_index) { |
3821 | 0 | RegCallFrame *f = &vm->frames[vm->frame_count - 1]; |
3822 | 0 | for (int i = 0; i < REGVM_REG_MAX; i++) |
3823 | 0 | value_free_inline(&f->regs[i]); |
3824 | 0 | vm->frame_count--; |
3825 | 0 | vm->reg_stack_top -= REGVM_REG_MAX; |
3826 | 0 | } |
3827 | |
|
3828 | 0 | frame = &vm->frames[vm->frame_count - 1]; |
3829 | 0 | R = frame->regs; |
3830 | 0 | frame->ip = h.ip; |
3831 | |
|
3832 | 0 | reg_set(&R[h.error_reg], thrown); |
3833 | 0 | DISPATCH(); |
3834 | 0 | } |
3835 | | |
3836 | 6 | CASE(TRY_UNWRAP) { |
3837 | 6 | uint8_t a = REG_GET_A(instr); |
3838 | | /* R[a] should be a Result map: {tag: "ok", value: ...} or {tag: "err", value: ...} */ |
3839 | 6 | if (R[a].type == VAL_MAP) { |
3840 | 5 | LatValue *tag = lat_map_get(R[a].as.map.map, "tag"); |
3841 | 5 | if (tag && tag->type == VAL_STR) { |
3842 | 5 | if (strcmp(tag->as.str_val, "ok") == 0) { |
3843 | 3 | LatValue *val = lat_map_get(R[a].as.map.map, "value"); |
3844 | 3 | LatValue unwrapped = val ? rvm_clone(val) : value_nil(); |
3845 | 3 | reg_set(&R[a], unwrapped); |
3846 | 3 | DISPATCH(); |
3847 | 3 | } else if (strcmp(tag->as.str_val, "err") == 0) { |
3848 | | /* Propagate error: return the error map */ |
3849 | 2 | LatValue err_val = rvm_clone(&R[a]); |
3850 | 2 | uint8_t dest_reg = frame->caller_result_reg; |
3851 | | |
3852 | 514 | for (int i = 0; i < REGVM_REG_MAX; i++) |
3853 | 512 | value_free_inline(&frame->regs[i]); |
3854 | 2 | vm->frame_count--; |
3855 | 2 | vm->reg_stack_top -= REGVM_REG_MAX; |
3856 | | |
3857 | 2 | if (vm->frame_count == base_frame) { |
3858 | 0 | *result = err_val; |
3859 | 0 | return REGVM_OK; |
3860 | 0 | } |
3861 | 2 | frame = &vm->frames[vm->frame_count - 1]; |
3862 | 2 | R = frame->regs; |
3863 | 2 | reg_set(&R[dest_reg], err_val); |
3864 | 2 | DISPATCH(); |
3865 | 2 | } |
3866 | 5 | } |
3867 | 5 | } |
3868 | | /* If it's an enum Result */ |
3869 | 6 | if (R[a].type == VAL_ENUM) { |
3870 | 0 | if (strcmp(R[a].as.enm.variant_name, "Ok") == 0) { |
3871 | 0 | LatValue unwrapped = R[a].as.enm.payload_count > 0 |
3872 | 0 | ? rvm_clone(&R[a].as.enm.payload[0]) : value_nil(); |
3873 | 0 | reg_set(&R[a], unwrapped); |
3874 | 0 | DISPATCH(); |
3875 | 0 | } else if (strcmp(R[a].as.enm.variant_name, "Err") == 0) { |
3876 | 0 | LatValue err_val = rvm_clone(&R[a]); |
3877 | 0 | uint8_t dest_reg = frame->caller_result_reg; |
3878 | 0 | for (int i = 0; i < REGVM_REG_MAX; i++) |
3879 | 0 | value_free_inline(&frame->regs[i]); |
3880 | 0 | vm->frame_count--; |
3881 | 0 | vm->reg_stack_top -= REGVM_REG_MAX; |
3882 | 0 | if (vm->frame_count == base_frame) { |
3883 | 0 | *result = err_val; |
3884 | 0 | return REGVM_OK; |
3885 | 0 | } |
3886 | 0 | frame = &vm->frames[vm->frame_count - 1]; |
3887 | 0 | R = frame->regs; |
3888 | 0 | reg_set(&R[dest_reg], err_val); |
3889 | 0 | DISPATCH(); |
3890 | 0 | } |
3891 | 0 | } |
3892 | | /* Not a Result — error */ |
3893 | 6 | RVM_ERROR("'?' operator requires a Result value, got %s", value_type_name(&R[a])); |
3894 | 6 | } |
3895 | | |
3896 | | /* ── Defer ── */ |
3897 | | |
3898 | 8 | CASE(DEFER_PUSH) { |
3899 | | /* A = scope_depth, sBx = offset to jump past the defer body */ |
3900 | 8 | uint8_t scope_d = REG_GET_A(instr); |
3901 | 8 | int16_t offset = REG_GET_sBx(instr); |
3902 | 8 | if (vm->defer_count >= REGVM_DEFER_MAX) |
3903 | 0 | RVM_ERROR("defer stack overflow"); |
3904 | 8 | RegDefer *d = &vm->defers[vm->defer_count++]; |
3905 | 8 | d->ip = frame->ip; /* Points to start of defer body */ |
3906 | 8 | d->chunk = frame->chunk; |
3907 | 8 | d->frame_index = (size_t)(vm->frame_count - 1); |
3908 | 8 | d->regs = frame->regs; |
3909 | 8 | d->scope_depth = (int)scope_d; |
3910 | | /* Skip past the defer body */ |
3911 | 8 | frame->ip += offset; |
3912 | 8 | DISPATCH(); |
3913 | 8 | } |
3914 | | |
3915 | 4.69k | CASE(DEFER_RUN) { |
3916 | | /* Execute defers for the current frame in LIFO order. |
3917 | | * A=min_scope_depth: only run defers with scope_depth >= A. |
3918 | | * A=0 runs all defers for this frame (used at function return). |
3919 | | * After each defer body runs, copy modified registers back to |
3920 | | * the original frame so deferred mutations are visible. */ |
3921 | 4.69k | uint8_t min_scope = REG_GET_A(instr); |
3922 | 4.69k | size_t frame_idx = (size_t)(vm->frame_count - 1); |
3923 | 4.69k | while (vm->defer_count > 0) { |
3924 | 8 | RegDefer *d = &vm->defers[vm->defer_count - 1]; |
3925 | 8 | if (d->frame_index != frame_idx) break; |
3926 | 8 | if (min_scope > 0 && d->scope_depth < (int)min_scope) break; |
3927 | 8 | vm->defer_count--; |
3928 | | |
3929 | | /* Push a new frame for the defer body */ |
3930 | 8 | if (vm->frame_count >= REGVM_FRAMES_MAX || |
3931 | 8 | vm->reg_stack_top + REGVM_REG_MAX > REGVM_REG_MAX * REGVM_FRAMES_MAX) { |
3932 | 0 | continue; /* Skip defer if stack is full */ |
3933 | 0 | } |
3934 | | |
3935 | 8 | LatValue *new_regs = &vm->reg_stack[vm->reg_stack_top]; |
3936 | 8 | vm->reg_stack_top += REGVM_REG_MAX; |
3937 | | |
3938 | | /* Copy current registers so defer body can access locals */ |
3939 | 2.05k | for (int i = 0; i < REGVM_REG_MAX; i++) |
3940 | 2.04k | new_regs[i] = rvm_clone(&R[i]); |
3941 | | |
3942 | 8 | RegCallFrame *df = &vm->frames[vm->frame_count++]; |
3943 | 8 | df->chunk = d->chunk; |
3944 | 8 | df->ip = d->ip; |
3945 | 8 | df->regs = new_regs; |
3946 | 8 | df->reg_count = REGVM_REG_MAX; |
3947 | 8 | df->upvalues = frame->upvalues; |
3948 | 8 | df->upvalue_count = frame->upvalue_count; |
3949 | 8 | df->caller_result_reg = 0; |
3950 | | |
3951 | 8 | LatValue defer_result; |
3952 | 8 | int saved_frame = (int)(vm->frame_count - 1); |
3953 | 8 | RegVMResult dr = regvm_dispatch(vm, saved_frame, &defer_result); |
3954 | 8 | value_free(&defer_result); |
3955 | 8 | (void)dr; |
3956 | | |
3957 | | /* HALT leaves the defer frame on the stack with registers intact. |
3958 | | * Copy modified registers back to the original frame, then pop. */ |
3959 | 8 | frame = &vm->frames[frame_idx]; |
3960 | 8 | R = frame->regs; |
3961 | | /* The defer frame may still be on the stack (HALT) or popped (error) */ |
3962 | 8 | if (vm->frame_count > (int)frame_idx + 1) { |
3963 | 8 | RegCallFrame *defer_frame = &vm->frames[vm->frame_count - 1]; |
3964 | 8 | LatValue *defer_regs = defer_frame->regs; |
3965 | 2.05k | for (int i = 0; i < REGVM_REG_MAX; i++) { |
3966 | 2.04k | value_free(&R[i]); |
3967 | 2.04k | R[i] = defer_regs[i]; |
3968 | 2.04k | defer_regs[i] = value_nil(); /* prevent double-free on cleanup */ |
3969 | 2.04k | } |
3970 | | /* Pop the defer frame */ |
3971 | 2.05k | for (int i = 0; i < REGVM_REG_MAX; i++) |
3972 | 2.04k | value_free_inline(&defer_frame->regs[i]); |
3973 | 8 | vm->frame_count--; |
3974 | 8 | vm->reg_stack_top -= REGVM_REG_MAX; |
3975 | 8 | } |
3976 | | /* Restore frame/R to the original frame */ |
3977 | 8 | frame = &vm->frames[frame_idx]; |
3978 | 8 | R = frame->regs; |
3979 | 8 | } |
3980 | 4.69k | DISPATCH(); |
3981 | 4.69k | } |
3982 | | |
3983 | | /* ── Variadic ── */ |
3984 | | |
3985 | 4.69k | CASE(COLLECT_VARARGS) { |
3986 | 12 | uint8_t a = REG_GET_A(instr); /* destination register */ |
3987 | 12 | uint8_t b = REG_GET_B(instr); /* start position (declared_arity + 1) */ |
3988 | | /* Collect excess args into an array */ |
3989 | 12 | size_t cap = 8; |
3990 | 12 | LatValue *elems = malloc(cap * sizeof(LatValue)); |
3991 | 12 | size_t count = 0; |
3992 | 29 | for (int i = b; i < REGVM_REG_MAX; i++) { |
3993 | 29 | if (R[i].type == VAL_NIL || R[i].type == VAL_UNIT) break; |
3994 | 17 | if (count >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); } |
3995 | 17 | elems[count++] = rvm_clone(&R[i]); |
3996 | 17 | } |
3997 | 12 | reg_set(&R[a], value_array(elems, count)); |
3998 | 12 | free(elems); |
3999 | 12 | DISPATCH(); |
4000 | 12 | } |
4001 | | |
4002 | | /* ── Advanced phase system ── */ |
4003 | | |
4004 | 206 | CASE(FREEZE_VAR) { |
4005 | | /* A=name constant index, B=loc_type (0=local, 1=upvalue, 2=global; high bit=consume seeds), C=slot */ |
4006 | 206 | uint8_t name_ki = REG_GET_A(instr); |
4007 | 206 | uint8_t raw_loc = REG_GET_B(instr); |
4008 | 206 | uint8_t slot = REG_GET_C(instr); |
4009 | 206 | bool consume_seeds = (raw_loc & 0x80) != 0; |
4010 | 206 | uint8_t loc_type = raw_loc & 0x7F; |
4011 | 206 | const char *var_name = frame->chunk->constants[name_ki].as.str_val; |
4012 | 206 | LatValue *target = NULL; |
4013 | 206 | if (loc_type == 0) { |
4014 | 205 | if (R[slot].type == VAL_CHANNEL) |
4015 | 1 | RVM_ERROR("cannot freeze a channel"); |
4016 | 204 | target = &R[slot]; |
4017 | 204 | } else if (loc_type == 1 && frame->upvalues && slot < frame->upvalue_count) { |
4018 | 0 | target = frame->upvalues[slot]->location; |
4019 | 1 | } else if (loc_type == 2) { |
4020 | 1 | LatValue gval; |
4021 | 1 | if (env_get(vm->env, var_name, &gval)) { |
4022 | | /* Validate seed contracts */ |
4023 | 1 | char *seed_err = rt_validate_seeds(vm->rt, var_name, &gval, consume_seeds); |
4024 | 1 | if (seed_err) { |
4025 | 0 | value_free(&gval); |
4026 | 0 | RVM_ERROR("%s", seed_err); |
4027 | 0 | } |
4028 | 1 | LatValue frozen = value_freeze(rvm_clone(&gval)); |
4029 | 1 | value_free(&gval); |
4030 | 1 | env_set(vm->env, var_name, frozen); |
4031 | 1 | rt_freeze_cascade(vm->rt, var_name); |
4032 | 1 | if (vm->rt->error) { vm->error = vm->rt->error; vm->rt->error = NULL; return REGVM_RUNTIME_ERROR; } if (vm->error) return REGVM_RUNTIME_ERROR; |
4033 | 1 | rt_fire_reactions(vm->rt, var_name, "crystal"); |
4034 | 1 | if (vm->rt->error) { vm->error = vm->rt->error; vm->rt->error = NULL; return REGVM_RUNTIME_ERROR; } if (vm->error) return REGVM_RUNTIME_ERROR; |
4035 | | /* Record history for tracked globals */ |
4036 | 1 | { |
4037 | 1 | if (vm->rt->tracking_active) |
4038 | 0 | rt_record_history(vm->rt, var_name, &frozen); |
4039 | 1 | } |
4040 | 1 | } |
4041 | 1 | DISPATCH(); |
4042 | 1 | } |
4043 | 205 | if (target) { |
4044 | | /* Validate seed contracts */ |
4045 | 204 | char *seed_err = rt_validate_seeds(vm->rt, var_name, target, consume_seeds); |
4046 | 204 | if (seed_err) { |
4047 | 2 | RVM_ERROR("%s", seed_err); |
4048 | 2 | } |
4049 | 202 | LatValue frozen = value_freeze(rvm_clone(target)); |
4050 | 202 | value_free(target); |
4051 | 202 | *target = frozen; |
4052 | | /* Sync to env for cascade/reactions (locals aren't in env) */ |
4053 | 202 | if (loc_type != 2) { /* not already global */ |
4054 | 202 | if (!env_set(vm->env, var_name, value_deep_clone(&frozen))) |
4055 | 39 | env_define(vm->env, var_name, value_deep_clone(&frozen)); |
4056 | 202 | } |
4057 | 202 | rt_freeze_cascade(vm->rt, var_name); |
4058 | 202 | if (vm->rt->error) { vm->error = vm->rt->error; vm->rt->error = NULL; return REGVM_RUNTIME_ERROR; } if (vm->error) return REGVM_RUNTIME_ERROR; |
4059 | 201 | rt_fire_reactions(vm->rt, var_name, "crystal"); |
4060 | 201 | if (vm->rt->error) { vm->error = vm->rt->error; vm->rt->error = NULL; return REGVM_RUNTIME_ERROR; } if (vm->error) return REGVM_RUNTIME_ERROR; |
4061 | | /* Record history for tracked variables after phase change */ |
4062 | 200 | { |
4063 | 200 | if (vm->rt->tracking_active) |
4064 | 2 | rt_record_history(vm->rt, var_name, target); |
4065 | 200 | } |
4066 | 200 | } |
4067 | 201 | DISPATCH(); |
4068 | 201 | } |
4069 | | |
4070 | 165 | CASE(THAW_VAR) { |
4071 | 165 | uint8_t name_ki = REG_GET_A(instr); |
4072 | 165 | uint8_t loc_type = REG_GET_B(instr); |
4073 | 165 | uint8_t slot = REG_GET_C(instr); |
4074 | 165 | const char *var_name = frame->chunk->constants[name_ki].as.str_val; |
4075 | | |
4076 | 165 | LatValue *target = NULL; |
4077 | 165 | if (loc_type == 0) { |
4078 | 164 | target = &R[slot]; |
4079 | 164 | } else if (loc_type == 1 && frame->upvalues && slot < frame->upvalue_count) { |
4080 | 0 | target = frame->upvalues[slot]->location; |
4081 | 1 | } else if (loc_type == 2) { |
4082 | 1 | LatValue gval; |
4083 | 1 | if (env_get(vm->env, var_name, &gval)) { |
4084 | 1 | LatValue thawed = value_thaw(&gval); |
4085 | 1 | value_free(&gval); |
4086 | 1 | env_set(vm->env, var_name, thawed); |
4087 | 1 | rt_fire_reactions(vm->rt, var_name, "fluid"); |
4088 | | /* Record history for tracked globals */ |
4089 | 1 | { |
4090 | 1 | if (vm->rt->tracking_active) |
4091 | 0 | rt_record_history(vm->rt, var_name, &thawed); |
4092 | 1 | } |
4093 | 1 | } |
4094 | 1 | DISPATCH(); |
4095 | 1 | } |
4096 | 165 | if (target) { |
4097 | 164 | LatValue thawed = value_thaw(target); |
4098 | 164 | value_free(target); |
4099 | 164 | *target = thawed; |
4100 | | /* Sync to env for cascade/reactions */ |
4101 | 164 | if (loc_type != 2) { |
4102 | 164 | if (!env_set(vm->env, var_name, value_deep_clone(&thawed))) |
4103 | 7 | env_define(vm->env, var_name, value_deep_clone(&thawed)); |
4104 | 164 | } |
4105 | 164 | rt_fire_reactions(vm->rt, var_name, "fluid"); |
4106 | | /* Record history for tracked variables after phase change */ |
4107 | 164 | { |
4108 | 164 | if (vm->rt->tracking_active) |
4109 | 1 | rt_record_history(vm->rt, var_name, target); |
4110 | 164 | } |
4111 | 164 | } |
4112 | 165 | DISPATCH(); |
4113 | 165 | } |
4114 | | |
4115 | 165 | CASE(SUBLIMATE_VAR) { |
4116 | 5 | uint8_t name_ki = REG_GET_A(instr); |
4117 | 5 | uint8_t loc_type = REG_GET_B(instr); |
4118 | 5 | uint8_t slot = REG_GET_C(instr); |
4119 | 5 | const char *var_name = frame->chunk->constants[name_ki].as.str_val; |
4120 | | |
4121 | 5 | LatValue *target = NULL; |
4122 | 5 | if (loc_type == 0) { |
4123 | 5 | target = &R[slot]; |
4124 | 5 | } else if (loc_type == 1 && frame->upvalues && slot < frame->upvalue_count) { |
4125 | 0 | target = frame->upvalues[slot]->location; |
4126 | 0 | } else if (loc_type == 2) { |
4127 | 0 | LatValue gval; |
4128 | 0 | if (env_get(vm->env, var_name, &gval)) { |
4129 | 0 | gval.phase = VTAG_SUBLIMATED; |
4130 | 0 | env_set(vm->env, var_name, gval); |
4131 | 0 | rt_fire_reactions(vm->rt, var_name, "sublimated"); |
4132 | 0 | } |
4133 | 0 | DISPATCH(); |
4134 | 0 | } |
4135 | 5 | if (target) { |
4136 | 5 | target->phase = VTAG_SUBLIMATED; |
4137 | | /* Sync to env */ |
4138 | 5 | if (loc_type != 2) { |
4139 | 5 | if (!env_set(vm->env, var_name, value_deep_clone(target))) |
4140 | 4 | env_define(vm->env, var_name, value_deep_clone(target)); |
4141 | 5 | } |
4142 | 5 | rt_fire_reactions(vm->rt, var_name, "sublimated"); |
4143 | 5 | } |
4144 | 5 | DISPATCH(); |
4145 | 5 | } |
4146 | | |
4147 | 9 | CASE(REACT) { |
4148 | | /* Compiler emits: emit_ABx(ROP_REACT, dst, name_ki) → A=cb_reg, Bx=name_ki */ |
4149 | 9 | uint8_t cb_reg = REG_GET_A(instr); |
4150 | 9 | uint16_t name_ki = REG_GET_Bx(instr); |
4151 | 9 | const char *var_name = frame->chunk->constants[name_ki].as.str_val; |
4152 | 9 | if (R[cb_reg].type != VAL_CLOSURE) DISPATCH(); |
4153 | | /* Find or create reaction entry */ |
4154 | 9 | size_t ri = vm->rt->reaction_count; |
4155 | 9 | for (size_t i = 0; i < vm->rt->reaction_count; i++) { |
4156 | 1 | if (strcmp(vm->rt->reactions[i].var_name, var_name) == 0) { ri = i; break; } |
4157 | 1 | } |
4158 | 9 | if (ri == vm->rt->reaction_count) { |
4159 | 8 | if (vm->rt->reaction_count >= vm->rt->reaction_cap) { |
4160 | 8 | vm->rt->reaction_cap = vm->rt->reaction_cap ? vm->rt->reaction_cap * 2 : 4; |
4161 | 8 | vm->rt->reactions = realloc(vm->rt->reactions, vm->rt->reaction_cap * sizeof(*vm->rt->reactions)); |
4162 | 8 | } |
4163 | 8 | vm->rt->reactions[ri].var_name = strdup(var_name); |
4164 | 8 | vm->rt->reactions[ri].callbacks = NULL; |
4165 | 8 | vm->rt->reactions[ri].cb_count = 0; |
4166 | 8 | vm->rt->reactions[ri].cb_cap = 0; |
4167 | 8 | vm->rt->reaction_count++; |
4168 | 8 | } |
4169 | 9 | if (vm->rt->reactions[ri].cb_count >= vm->rt->reactions[ri].cb_cap) { |
4170 | 8 | vm->rt->reactions[ri].cb_cap = vm->rt->reactions[ri].cb_cap ? vm->rt->reactions[ri].cb_cap * 2 : 4; |
4171 | 8 | vm->rt->reactions[ri].callbacks = realloc(vm->rt->reactions[ri].callbacks, |
4172 | 8 | vm->rt->reactions[ri].cb_cap * sizeof(LatValue)); |
4173 | 8 | } |
4174 | 9 | vm->rt->reactions[ri].callbacks[vm->rt->reactions[ri].cb_count++] = value_deep_clone(&R[cb_reg]); |
4175 | 9 | DISPATCH(); |
4176 | 9 | } |
4177 | | |
4178 | 9 | CASE(UNREACT) { |
4179 | | /* Compiler emits: emit_ABx(ROP_UNREACT, dst, name_ki) → A=dst, Bx=name_ki */ |
4180 | 1 | uint16_t name_ki = REG_GET_Bx(instr); |
4181 | 1 | const char *var_name = frame->chunk->constants[name_ki].as.str_val; |
4182 | 1 | for (size_t i = 0; i < vm->rt->reaction_count; i++) { |
4183 | 1 | if (strcmp(vm->rt->reactions[i].var_name, var_name) != 0) continue; |
4184 | 1 | free(vm->rt->reactions[i].var_name); |
4185 | 2 | for (size_t j = 0; j < vm->rt->reactions[i].cb_count; j++) |
4186 | 1 | value_free(&vm->rt->reactions[i].callbacks[j]); |
4187 | 1 | free(vm->rt->reactions[i].callbacks); |
4188 | 1 | vm->rt->reactions[i] = vm->rt->reactions[--vm->rt->reaction_count]; |
4189 | 1 | break; |
4190 | 1 | } |
4191 | 1 | DISPATCH(); |
4192 | 1 | } |
4193 | | |
4194 | 17 | CASE(BOND) { |
4195 | | /* Compiler emits: emit_ABC(ROP_BOND, target_ki, dep_reg, strat_reg) → A=target_ki, B=dep_reg, C=strat_reg */ |
4196 | 17 | uint8_t target_ki = REG_GET_A(instr); |
4197 | 17 | uint8_t dep_reg = REG_GET_B(instr); |
4198 | 17 | uint8_t strat_reg = REG_GET_C(instr); |
4199 | 17 | const char *target_name = frame->chunk->constants[target_ki].as.str_val; |
4200 | 17 | const char *dep_name = (R[dep_reg].type == VAL_STR) ? R[dep_reg].as.str_val : ""; |
4201 | 17 | const char *strategy = (R[strat_reg].type == VAL_STR) ? R[strat_reg].as.str_val : "mirror"; |
4202 | 17 | if (dep_name[0] == '\0') { |
4203 | 1 | RVM_ERROR("bond() requires variable names for dependencies"); |
4204 | 1 | } |
4205 | | /* Validate: check variables exist and target is not already frozen */ |
4206 | 16 | { |
4207 | | /* Find target variable's phase */ |
4208 | 16 | PhaseTag target_phase = VTAG_UNPHASED; |
4209 | 16 | LatValue tval; |
4210 | 16 | bool t_env = env_get(vm->env, target_name, &tval); |
4211 | 16 | if (t_env) { target_phase = tval.phase; value_free(&tval); } |
4212 | 15 | else { |
4213 | 30 | for (int fi = 0; fi < (int)vm->frame_count; fi++) { |
4214 | 30 | RegCallFrame *f = &vm->frames[fi]; |
4215 | 30 | if (!f->chunk || !f->chunk->local_names) continue; |
4216 | 31 | for (size_t r = 0; r < f->chunk->local_name_cap; r++) { |
4217 | 31 | if (f->chunk->local_names[r] && strcmp(f->chunk->local_names[r], target_name) == 0) { |
4218 | 15 | target_phase = f->regs[r].phase; |
4219 | 15 | goto found_target; |
4220 | 15 | } |
4221 | 31 | } |
4222 | 15 | } |
4223 | 15 | found_target:; |
4224 | 15 | } |
4225 | 16 | if (target_phase == VTAG_CRYSTAL) |
4226 | 1 | RVM_ERROR("bond: variable '%s' is already frozen", target_name); |
4227 | | |
4228 | | /* Check dep variable exists */ |
4229 | 15 | LatValue dep_val; |
4230 | 15 | bool dep_found = env_get(vm->env, dep_name, &dep_val); |
4231 | 15 | if (dep_found) { value_free(&dep_val); } |
4232 | 14 | else { |
4233 | 14 | bool found_local = false; |
4234 | 29 | for (int fi = 0; fi < (int)vm->frame_count; fi++) { |
4235 | 28 | RegCallFrame *f = &vm->frames[fi]; |
4236 | 28 | if (!f->chunk || !f->chunk->local_names) continue; |
4237 | 60 | for (size_t r = 0; r < f->chunk->local_name_cap; r++) { |
4238 | 59 | if (f->chunk->local_names[r] && strcmp(f->chunk->local_names[r], dep_name) == 0) { |
4239 | 13 | found_local = true; break; |
4240 | 13 | } |
4241 | 59 | } |
4242 | 14 | if (found_local) break; |
4243 | 14 | } |
4244 | 14 | if (!found_local) |
4245 | 1 | RVM_ERROR("bond: undefined variable '%s'", dep_name); |
4246 | 14 | } |
4247 | 15 | } |
4248 | | /* Find or create bond entry */ |
4249 | 14 | size_t bi = vm->rt->bond_count; |
4250 | 15 | for (size_t i = 0; i < vm->rt->bond_count; i++) { |
4251 | 3 | if (strcmp(vm->rt->bonds[i].target, target_name) == 0) { bi = i; break; } |
4252 | 3 | } |
4253 | 14 | if (bi == vm->rt->bond_count) { |
4254 | 12 | if (vm->rt->bond_count >= vm->rt->bond_cap) { |
4255 | 11 | vm->rt->bond_cap = vm->rt->bond_cap ? vm->rt->bond_cap * 2 : 4; |
4256 | 11 | vm->rt->bonds = realloc(vm->rt->bonds, vm->rt->bond_cap * sizeof(*vm->rt->bonds)); |
4257 | 11 | } |
4258 | 12 | vm->rt->bonds[bi].target = strdup(target_name); |
4259 | 12 | vm->rt->bonds[bi].deps = NULL; |
4260 | 12 | vm->rt->bonds[bi].dep_strategies = NULL; |
4261 | 12 | vm->rt->bonds[bi].dep_count = 0; |
4262 | 12 | vm->rt->bonds[bi].dep_cap = 0; |
4263 | 12 | vm->rt->bond_count++; |
4264 | 12 | } |
4265 | 14 | if (vm->rt->bonds[bi].dep_count >= vm->rt->bonds[bi].dep_cap) { |
4266 | 12 | vm->rt->bonds[bi].dep_cap = vm->rt->bonds[bi].dep_cap ? vm->rt->bonds[bi].dep_cap * 2 : 4; |
4267 | 12 | vm->rt->bonds[bi].deps = realloc(vm->rt->bonds[bi].deps, |
4268 | 12 | vm->rt->bonds[bi].dep_cap * sizeof(char *)); |
4269 | 12 | vm->rt->bonds[bi].dep_strategies = realloc(vm->rt->bonds[bi].dep_strategies, |
4270 | 12 | vm->rt->bonds[bi].dep_cap * sizeof(char *)); |
4271 | 12 | } |
4272 | 14 | vm->rt->bonds[bi].deps[vm->rt->bonds[bi].dep_count] = strdup(dep_name); |
4273 | 14 | vm->rt->bonds[bi].dep_strategies[vm->rt->bonds[bi].dep_count] = strdup(strategy); |
4274 | 14 | vm->rt->bonds[bi].dep_count++; |
4275 | 14 | DISPATCH(); |
4276 | 14 | } |
4277 | | |
4278 | 1 | CASE(UNBOND) { |
4279 | | /* Compiler emits: emit_ABx(ROP_UNBOND, target_ki, dep_reg) → A=target_ki, Bx=dep_reg */ |
4280 | 1 | uint8_t target_ki = REG_GET_A(instr); |
4281 | 1 | uint8_t dep_reg = (uint8_t)REG_GET_Bx(instr); |
4282 | 1 | const char *target_name = frame->chunk->constants[target_ki].as.str_val; |
4283 | 1 | const char *dep_name = (R[dep_reg].type == VAL_STR) ? R[dep_reg].as.str_val : ""; |
4284 | 1 | for (size_t i = 0; i < vm->rt->bond_count; i++) { |
4285 | 1 | if (strcmp(vm->rt->bonds[i].target, target_name) != 0) continue; |
4286 | 1 | for (size_t j = 0; j < vm->rt->bonds[i].dep_count; j++) { |
4287 | 1 | if (strcmp(vm->rt->bonds[i].deps[j], dep_name) != 0) continue; |
4288 | 1 | free(vm->rt->bonds[i].deps[j]); |
4289 | 1 | if (vm->rt->bonds[i].dep_strategies) |
4290 | 1 | free(vm->rt->bonds[i].dep_strategies[j]); |
4291 | 1 | vm->rt->bonds[i].deps[j] = vm->rt->bonds[i].deps[vm->rt->bonds[i].dep_count - 1]; |
4292 | 1 | if (vm->rt->bonds[i].dep_strategies) |
4293 | 1 | vm->rt->bonds[i].dep_strategies[j] = vm->rt->bonds[i].dep_strategies[vm->rt->bonds[i].dep_count - 1]; |
4294 | 1 | vm->rt->bonds[i].dep_count--; |
4295 | 1 | break; |
4296 | 1 | } |
4297 | 1 | if (vm->rt->bonds[i].dep_count == 0) { |
4298 | 1 | free(vm->rt->bonds[i].target); |
4299 | 1 | free(vm->rt->bonds[i].deps); |
4300 | 1 | free(vm->rt->bonds[i].dep_strategies); |
4301 | 1 | vm->rt->bonds[i] = vm->rt->bonds[--vm->rt->bond_count]; |
4302 | 1 | } |
4303 | 1 | break; |
4304 | 1 | } |
4305 | 1 | DISPATCH(); |
4306 | 1 | } |
4307 | | |
4308 | 4 | CASE(SEED) { |
4309 | | /* Compiler emits: emit_ABx(ROP_SEED, dst, name_ki) → A=contract_reg, Bx=name_ki */ |
4310 | 4 | uint8_t contract_reg = REG_GET_A(instr); |
4311 | 4 | uint16_t name_ki = REG_GET_Bx(instr); |
4312 | 4 | const char *var_name = frame->chunk->constants[name_ki].as.str_val; |
4313 | 4 | if (R[contract_reg].type != VAL_CLOSURE) DISPATCH(); |
4314 | 4 | if (vm->rt->seed_count >= vm->rt->seed_cap) { |
4315 | 4 | vm->rt->seed_cap = vm->rt->seed_cap ? vm->rt->seed_cap * 2 : 4; |
4316 | 4 | vm->rt->seeds = realloc(vm->rt->seeds, vm->rt->seed_cap * sizeof(*vm->rt->seeds)); |
4317 | 4 | } |
4318 | 4 | vm->rt->seeds[vm->rt->seed_count].var_name = strdup(var_name); |
4319 | 4 | vm->rt->seeds[vm->rt->seed_count].contract = value_deep_clone(&R[contract_reg]); |
4320 | 4 | vm->rt->seed_count++; |
4321 | 4 | DISPATCH(); |
4322 | 4 | } |
4323 | | |
4324 | 4 | CASE(UNSEED) { |
4325 | | /* Compiler emits: emit_ABx(ROP_UNSEED, dst, name_ki) → A=dst, Bx=name_ki */ |
4326 | 1 | uint16_t name_ki = REG_GET_Bx(instr); |
4327 | 1 | const char *var_name = frame->chunk->constants[name_ki].as.str_val; |
4328 | 1 | for (size_t i = 0; i < vm->rt->seed_count; i++) { |
4329 | 1 | if (strcmp(vm->rt->seeds[i].var_name, var_name) != 0) continue; |
4330 | 1 | free(vm->rt->seeds[i].var_name); |
4331 | 1 | value_free(&vm->rt->seeds[i].contract); |
4332 | 1 | vm->rt->seeds[i] = vm->rt->seeds[--vm->rt->seed_count]; |
4333 | 1 | break; |
4334 | 1 | } |
4335 | 1 | DISPATCH(); |
4336 | 1 | } |
4337 | | |
4338 | | /* ── Module/Import ── */ |
4339 | | |
4340 | 94 | CASE(IMPORT) { |
4341 | 94 | uint8_t a = REG_GET_A(instr); |
4342 | 94 | uint16_t bx = REG_GET_Bx(instr); |
4343 | 94 | const char *raw_path = frame->chunk->constants[bx].as.str_val; |
4344 | | |
4345 | | /* Check for built-in stdlib module */ |
4346 | 94 | LatValue builtin_mod; |
4347 | 94 | if (rt_try_builtin_import(raw_path, &builtin_mod)) { |
4348 | 11 | reg_set(&R[a], builtin_mod); |
4349 | 11 | DISPATCH(); |
4350 | 11 | } |
4351 | | |
4352 | | /* Resolve file path: append .lat if not present */ |
4353 | 94 | size_t plen = strlen(raw_path); |
4354 | 94 | char *file_path; |
4355 | 94 | if (plen >= 4 && strcmp(raw_path + plen - 4, ".lat") == 0) { |
4356 | 0 | file_path = strdup(raw_path); |
4357 | 94 | } else { |
4358 | 94 | file_path = malloc(plen + 5); |
4359 | 94 | memcpy(file_path, raw_path, plen); |
4360 | 94 | memcpy(file_path + plen, ".lat", 5); |
4361 | 94 | } |
4362 | | |
4363 | | /* Resolve to absolute path */ |
4364 | 94 | char resolved[PATH_MAX]; |
4365 | 94 | if (!realpath(file_path, resolved)) { |
4366 | 1 | char *emsg = NULL; |
4367 | 1 | (void)asprintf(&emsg, "import: cannot find '%s'", file_path); |
4368 | 1 | free(file_path); |
4369 | | /* Set error directly without [line N] prefix for import errors */ |
4370 | 1 | vm->error = emsg; |
4371 | 1 | return REGVM_RUNTIME_ERROR; |
4372 | 1 | } |
4373 | 93 | free(file_path); |
4374 | | |
4375 | | /* Check module cache */ |
4376 | 93 | if (vm->module_cache) { |
4377 | 1 | LatValue *cached = lat_map_get(vm->module_cache, resolved); |
4378 | 1 | if (cached) { |
4379 | 1 | reg_set(&R[a], rvm_clone(cached)); |
4380 | 1 | DISPATCH(); |
4381 | 1 | } |
4382 | 1 | } |
4383 | | |
4384 | | /* Read the file */ |
4385 | 93 | char *source = builtin_read_file(resolved); |
4386 | 93 | if (!source) |
4387 | 0 | RVM_ERROR("import: cannot read '%s'", resolved); |
4388 | | |
4389 | | /* Lex */ |
4390 | 93 | Lexer mod_lex = lexer_new(source); |
4391 | 93 | char *lex_err = NULL; |
4392 | 93 | LatVec mod_toks = lexer_tokenize(&mod_lex, &lex_err); |
4393 | 93 | free(source); |
4394 | 93 | if (lex_err) { |
4395 | 0 | char errmsg[1024]; |
4396 | 0 | snprintf(errmsg, sizeof(errmsg), "import '%s': %s", resolved, lex_err); |
4397 | 0 | free(lex_err); |
4398 | 0 | lat_vec_free(&mod_toks); |
4399 | 0 | RVM_ERROR("%s", errmsg); |
4400 | 0 | } |
4401 | | |
4402 | | /* Parse */ |
4403 | 93 | Parser mod_parser = parser_new(&mod_toks); |
4404 | 93 | char *parse_err = NULL; |
4405 | 93 | Program mod_prog = parser_parse(&mod_parser, &parse_err); |
4406 | 93 | if (parse_err) { |
4407 | 0 | char errmsg[1024]; |
4408 | 0 | snprintf(errmsg, sizeof(errmsg), "import '%s': %s", resolved, parse_err); |
4409 | 0 | free(parse_err); |
4410 | 0 | program_free(&mod_prog); |
4411 | 0 | for (size_t ti = 0; ti < mod_toks.len; ti++) |
4412 | 0 | token_free(lat_vec_get(&mod_toks, ti)); |
4413 | 0 | lat_vec_free(&mod_toks); |
4414 | 0 | RVM_ERROR("%s", errmsg); |
4415 | 0 | } |
4416 | | |
4417 | | /* Compile as module */ |
4418 | 93 | char *comp_err = NULL; |
4419 | 93 | RegChunk *mod_chunk = reg_compile_module(&mod_prog, &comp_err); |
4420 | | |
4421 | | /* Free parse artifacts */ |
4422 | 93 | program_free(&mod_prog); |
4423 | 117k | for (size_t ti = 0; ti < mod_toks.len; ti++) |
4424 | 117k | token_free(lat_vec_get(&mod_toks, ti)); |
4425 | 93 | lat_vec_free(&mod_toks); |
4426 | | |
4427 | 93 | if (!mod_chunk) { |
4428 | 0 | char errmsg[1024]; |
4429 | 0 | snprintf(errmsg, sizeof(errmsg), "import '%s': %s", resolved, |
4430 | 0 | comp_err ? comp_err : "compile error"); |
4431 | 0 | free(comp_err); |
4432 | 0 | RVM_ERROR("%s", errmsg); |
4433 | 0 | } |
4434 | | |
4435 | | /* Track chunk */ |
4436 | 93 | regvm_track_chunk(vm, mod_chunk); |
4437 | | |
4438 | | /* Push module scope */ |
4439 | 93 | env_push_scope(vm->env); |
4440 | | |
4441 | | /* Run module by pushing a new frame */ |
4442 | 93 | LatValue mod_result; |
4443 | 93 | RegVMResult mod_r = regvm_run_sub(vm, mod_chunk, &mod_result); |
4444 | | /* Restore frame/R pointers after dispatch */ |
4445 | 93 | frame = &vm->frames[vm->frame_count - 1]; |
4446 | 93 | R = frame->regs; |
4447 | | |
4448 | 93 | if (mod_r != REGVM_OK) { |
4449 | 0 | env_pop_scope(vm->env); |
4450 | 0 | reg_set(&R[a], value_nil()); |
4451 | 0 | DISPATCH(); |
4452 | 0 | } |
4453 | 93 | value_free(&mod_result); |
4454 | | |
4455 | | /* Build module Map from the module scope */ |
4456 | 93 | LatValue module_map = value_map_new(); |
4457 | 93 | Scope *mod_scope = &vm->env->scopes[vm->env->count - 1]; |
4458 | 3.22k | for (size_t mi = 0; mi < mod_scope->cap; mi++) { |
4459 | 3.13k | if (mod_scope->entries[mi].state != MAP_OCCUPIED) continue; |
4460 | 1.77k | const char *name = mod_scope->entries[mi].key; |
4461 | 1.77k | LatValue *val_ptr = (LatValue *)mod_scope->entries[mi].value; |
4462 | | |
4463 | | /* Copy all module bindings to base scope for closures */ |
4464 | 1.77k | env_define_at(vm->env, 0, name, value_deep_clone(val_ptr)); |
4465 | | |
4466 | | /* Filter based on export declarations */ |
4467 | 1.77k | if (!module_should_export(name, |
4468 | 1.77k | (const char **)mod_chunk->export_names, |
4469 | 1.77k | mod_chunk->export_count, mod_chunk->has_exports)) |
4470 | 12 | continue; |
4471 | | |
4472 | 1.76k | LatValue exported = rvm_clone(val_ptr); |
4473 | 1.76k | lat_map_set(module_map.as.map.map, name, &exported); |
4474 | 1.76k | } |
4475 | | |
4476 | 93 | env_pop_scope(vm->env); |
4477 | | |
4478 | | /* Cache */ |
4479 | 93 | if (!vm->module_cache) { |
4480 | 81 | vm->module_cache = malloc(sizeof(LatMap)); |
4481 | 81 | *vm->module_cache = lat_map_new(sizeof(LatValue)); |
4482 | 81 | } |
4483 | 93 | LatValue cache_copy = value_deep_clone(&module_map); |
4484 | 93 | lat_map_set(vm->module_cache, resolved, &cache_copy); |
4485 | | |
4486 | 93 | reg_set(&R[a], module_map); |
4487 | 93 | DISPATCH(); |
4488 | 93 | } |
4489 | | |
4490 | 9 | CASE(REQUIRE) { |
4491 | 9 | uint8_t a = REG_GET_A(instr); |
4492 | 9 | uint16_t bx = REG_GET_Bx(instr); |
4493 | 9 | const char *raw_path = frame->chunk->constants[bx].as.str_val; |
4494 | | |
4495 | | /* Resolve file path: append .lat if not present */ |
4496 | 9 | size_t plen = strlen(raw_path); |
4497 | 9 | char *file_path; |
4498 | 9 | if (plen >= 4 && strcmp(raw_path + plen - 4, ".lat") == 0) { |
4499 | 2 | file_path = strdup(raw_path); |
4500 | 7 | } else { |
4501 | 7 | file_path = malloc(plen + 5); |
4502 | 7 | memcpy(file_path, raw_path, plen); |
4503 | 7 | memcpy(file_path + plen, ".lat", 5); |
4504 | 7 | } |
4505 | | |
4506 | | /* Resolve to absolute path: try CWD first, then script_dir */ |
4507 | 9 | char resolved[PATH_MAX]; |
4508 | 9 | bool found = (realpath(file_path, resolved) != NULL); |
4509 | 9 | if (!found && vm->rt->script_dir && file_path[0] != '/') { |
4510 | 0 | char script_rel[PATH_MAX]; |
4511 | 0 | snprintf(script_rel, sizeof(script_rel), "%s/%s", |
4512 | 0 | vm->rt->script_dir, file_path); |
4513 | 0 | found = (realpath(script_rel, resolved) != NULL); |
4514 | 0 | } |
4515 | 9 | if (!found) { |
4516 | 1 | char *emsg = NULL; |
4517 | 1 | (void)asprintf(&emsg, "require: cannot find '%s'", raw_path); |
4518 | 1 | free(file_path); |
4519 | | /* Set error directly without [line N] prefix, matching native_require */ |
4520 | 1 | vm->error = emsg; |
4521 | 1 | return REGVM_RUNTIME_ERROR; |
4522 | 1 | } |
4523 | 8 | free(file_path); |
4524 | | |
4525 | | /* Dedup: skip if already required */ |
4526 | 8 | if (vm->module_cache) { |
4527 | 3 | LatValue *cached = lat_map_get(vm->module_cache, resolved); |
4528 | 3 | if (cached) { |
4529 | 2 | reg_set(&R[a], value_bool(true)); |
4530 | 2 | DISPATCH(); |
4531 | 2 | } |
4532 | 3 | } |
4533 | | |
4534 | | /* Read the file */ |
4535 | 8 | char *source = builtin_read_file(resolved); |
4536 | 8 | if (!source) |
4537 | 0 | RVM_ERROR("require: cannot read '%s'", resolved); |
4538 | | |
4539 | | /* Lex */ |
4540 | 8 | Lexer req_lex = lexer_new(source); |
4541 | 8 | char *lex_err = NULL; |
4542 | 8 | LatVec req_toks = lexer_tokenize(&req_lex, &lex_err); |
4543 | 8 | free(source); |
4544 | 8 | if (lex_err) { |
4545 | 0 | char errmsg[1024]; |
4546 | 0 | snprintf(errmsg, sizeof(errmsg), "require '%s': %s", resolved, lex_err); |
4547 | 0 | free(lex_err); |
4548 | 0 | lat_vec_free(&req_toks); |
4549 | 0 | RVM_ERROR("%s", errmsg); |
4550 | 0 | } |
4551 | | |
4552 | | /* Parse */ |
4553 | 8 | Parser req_parser = parser_new(&req_toks); |
4554 | 8 | char *parse_err = NULL; |
4555 | 8 | Program req_prog = parser_parse(&req_parser, &parse_err); |
4556 | 8 | if (parse_err) { |
4557 | 0 | char errmsg[1024]; |
4558 | 0 | snprintf(errmsg, sizeof(errmsg), "require '%s': %s", resolved, parse_err); |
4559 | 0 | free(parse_err); |
4560 | 0 | program_free(&req_prog); |
4561 | 0 | for (size_t ti = 0; ti < req_toks.len; ti++) |
4562 | 0 | token_free(lat_vec_get(&req_toks, ti)); |
4563 | 0 | lat_vec_free(&req_toks); |
4564 | 0 | RVM_ERROR("%s", errmsg); |
4565 | 0 | } |
4566 | | |
4567 | | /* Compile as module (via regcompiler, not stack VM compiler) */ |
4568 | 8 | char *comp_err = NULL; |
4569 | 8 | RegChunk *req_chunk = reg_compile_module(&req_prog, &comp_err); |
4570 | | |
4571 | | /* Free parse artifacts */ |
4572 | 8 | program_free(&req_prog); |
4573 | 109 | for (size_t ti = 0; ti < req_toks.len; ti++) |
4574 | 101 | token_free(lat_vec_get(&req_toks, ti)); |
4575 | 8 | lat_vec_free(&req_toks); |
4576 | | |
4577 | 8 | if (!req_chunk) { |
4578 | 0 | char errmsg[1024]; |
4579 | 0 | snprintf(errmsg, sizeof(errmsg), "require '%s': %s", resolved, |
4580 | 0 | comp_err ? comp_err : "compile error"); |
4581 | 0 | free(comp_err); |
4582 | 0 | RVM_ERROR("%s", errmsg); |
4583 | 0 | } |
4584 | | |
4585 | | /* Track chunk */ |
4586 | 8 | regvm_track_chunk(vm, req_chunk); |
4587 | | |
4588 | | /* Mark as loaded (for dedup) before execution */ |
4589 | 8 | if (!vm->module_cache) { |
4590 | 5 | vm->module_cache = malloc(sizeof(LatMap)); |
4591 | 5 | *vm->module_cache = lat_map_new(sizeof(LatValue)); |
4592 | 5 | } |
4593 | 8 | LatValue loaded_marker = value_bool(true); |
4594 | 8 | lat_map_set(vm->module_cache, resolved, &loaded_marker); |
4595 | | |
4596 | | /* Run module — NO scope isolation, defs go directly to global env */ |
4597 | 8 | LatValue req_result; |
4598 | 8 | RegVMResult req_r = regvm_run_sub(vm, req_chunk, &req_result); |
4599 | | /* Restore frame/R pointers after dispatch */ |
4600 | 8 | frame = &vm->frames[vm->frame_count - 1]; |
4601 | 8 | R = frame->regs; |
4602 | | |
4603 | 8 | if (req_r != REGVM_OK) { |
4604 | | /* Propagate the error */ |
4605 | 0 | return REGVM_RUNTIME_ERROR; |
4606 | 0 | } |
4607 | 8 | value_free(&req_result); |
4608 | | |
4609 | 8 | reg_set(&R[a], value_bool(true)); |
4610 | 8 | DISPATCH(); |
4611 | 8 | } |
4612 | | |
4613 | | /* ── Concurrency ── */ |
4614 | | |
4615 | 4 | CASE(SCOPE) { |
4616 | 4 | uint8_t dst_reg = REG_GET_A(instr); |
4617 | | /* Variable-length: read spawn_count, sync_idx, spawn_indices */ |
4618 | 4 | RegInstr data1 = READ_INSTR(); |
4619 | 4 | uint8_t spawn_count = REG_GET_A(data1); |
4620 | 4 | uint8_t sync_idx = REG_GET_B(data1); |
4621 | 4 | uint8_t spawn_indices[256]; |
4622 | | /* Read spawn indices from follow-up data words (3 per word: A, B, C) */ |
4623 | 6 | for (uint8_t i = 0; i < spawn_count; i += 3) { |
4624 | 2 | RegInstr sp = READ_INSTR(); |
4625 | 2 | spawn_indices[i] = REG_GET_A(sp); |
4626 | 2 | if (i + 1 < spawn_count) spawn_indices[i + 1] = REG_GET_B(sp); |
4627 | 2 | if (i + 2 < spawn_count) spawn_indices[i + 2] = REG_GET_C(sp); |
4628 | 2 | } |
4629 | | |
4630 | | /* Export locals to env for sub-chunk access */ |
4631 | 4 | env_push_scope(vm->env); |
4632 | 12 | for (int fi2 = 0; fi2 < vm->frame_count; fi2++) { |
4633 | 8 | RegCallFrame *f2 = &vm->frames[fi2]; |
4634 | 8 | if (!f2->chunk) continue; |
4635 | 59 | for (size_t sl = 0; sl < f2->chunk->local_name_cap; sl++) { |
4636 | 51 | if (f2->chunk->local_names[sl]) |
4637 | 6 | env_define(vm->env, f2->chunk->local_names[sl], |
4638 | 6 | rvm_clone(&f2->regs[sl])); |
4639 | 51 | } |
4640 | 8 | } |
4641 | | |
4642 | | /* Run sync body — its return value becomes the scope result */ |
4643 | 4 | LatValue scope_result = value_unit(); |
4644 | 4 | if (sync_idx != 0xFF) { |
4645 | 2 | RegChunk *sync_body = (RegChunk *)frame->chunk->constants[sync_idx].as.closure.native_fn; |
4646 | 2 | if (sync_body) { |
4647 | 2 | RegVMResult sr = regvm_run_sub(vm, sync_body, &scope_result); |
4648 | | /* Restore frame/R pointers */ |
4649 | 2 | frame = &vm->frames[vm->frame_count - 1]; |
4650 | 2 | R = frame->regs; |
4651 | 2 | if (sr != REGVM_OK) { |
4652 | 0 | env_pop_scope(vm->env); |
4653 | 0 | RVM_ERROR("%s", vm->error ? vm->error : "scope error"); |
4654 | 0 | } |
4655 | 2 | } |
4656 | 2 | } |
4657 | | |
4658 | | /* Run spawns synchronously (threading TBD) */ |
4659 | 6 | for (uint8_t i = 0; i < spawn_count; i++) { |
4660 | 3 | RegChunk *sp_chunk = (RegChunk *)frame->chunk->constants[spawn_indices[i]].as.closure.native_fn; |
4661 | 3 | if (sp_chunk) { |
4662 | 3 | LatValue sp_result; |
4663 | 3 | RegVMResult sr = regvm_run_sub(vm, sp_chunk, &sp_result); |
4664 | | /* Restore frame/R pointers */ |
4665 | 3 | frame = &vm->frames[vm->frame_count - 1]; |
4666 | 3 | R = frame->regs; |
4667 | 3 | value_free(&sp_result); |
4668 | 3 | if (sr != REGVM_OK) { |
4669 | 1 | value_free(&scope_result); |
4670 | 1 | env_pop_scope(vm->env); |
4671 | 1 | RVM_ERROR("%s", vm->error ? vm->error : "spawn error"); |
4672 | 1 | } |
4673 | 3 | } |
4674 | 3 | } |
4675 | | |
4676 | 3 | env_pop_scope(vm->env); |
4677 | 3 | reg_set(&R[dst_reg], scope_result); |
4678 | 3 | DISPATCH(); |
4679 | 3 | } |
4680 | | |
4681 | 5 | CASE(SELECT) { |
4682 | 5 | uint8_t dst_reg = REG_GET_A(instr); |
4683 | | /* Variable-length: arm_count, per-arm data */ |
4684 | 5 | RegInstr data1 = READ_INSTR(); |
4685 | 5 | uint8_t arm_count = REG_GET_A(data1); |
4686 | | |
4687 | | /* Read all arm descriptors (2 data words per arm) */ |
4688 | 5 | typedef struct { uint8_t flags, chan_idx, body_idx, binding_idx; } RSelArm; |
4689 | 5 | RSelArm sel_arms[64]; |
4690 | 14 | for (uint8_t i = 0; i < arm_count && i < 64; i++) { |
4691 | 9 | RegInstr d1 = READ_INSTR(); |
4692 | 9 | RegInstr d2 = READ_INSTR(); |
4693 | 9 | sel_arms[i].flags = REG_GET_A(d1); |
4694 | 9 | sel_arms[i].chan_idx = REG_GET_B(d1); |
4695 | 9 | sel_arms[i].body_idx = REG_GET_C(d1); |
4696 | 9 | sel_arms[i].binding_idx = REG_GET_A(d2); |
4697 | 9 | } |
4698 | | |
4699 | | /* Export locals to env for sub-chunk visibility */ |
4700 | 5 | env_push_scope(vm->env); |
4701 | 15 | for (int fi2 = 0; fi2 < vm->frame_count; fi2++) { |
4702 | 10 | RegCallFrame *f2 = &vm->frames[fi2]; |
4703 | 10 | if (!f2->chunk) continue; |
4704 | 95 | for (size_t sl = 0; sl < f2->chunk->local_name_cap; sl++) { |
4705 | 85 | if (f2->chunk->local_names[sl]) |
4706 | 11 | env_define(vm->env, f2->chunk->local_names[sl], |
4707 | 11 | rvm_clone(&f2->regs[sl])); |
4708 | 85 | } |
4709 | 10 | } |
4710 | | |
4711 | | /* Find default arm */ |
4712 | 5 | int default_arm = -1; |
4713 | 11 | for (uint8_t i = 0; i < arm_count; i++) { |
4714 | 9 | if (sel_arms[i].flags & 0x01) { default_arm = (int)i; break; } |
4715 | 9 | } |
4716 | | |
4717 | | /* Evaluate channel expressions */ |
4718 | 5 | LatChannel **channels = calloc(arm_count, sizeof(LatChannel *)); |
4719 | 14 | for (uint8_t i = 0; i < arm_count; i++) { |
4720 | 9 | if (sel_arms[i].flags & 0x03) continue; /* skip default/timeout */ |
4721 | 6 | RegChunk *ch_chunk = (RegChunk *)frame->chunk->constants[sel_arms[i].chan_idx].as.closure.native_fn; |
4722 | 6 | LatValue ch_val; |
4723 | 6 | RegVMResult cr = regvm_run_sub(vm, ch_chunk, &ch_val); |
4724 | 6 | frame = &vm->frames[vm->frame_count - 1]; |
4725 | 6 | R = frame->regs; |
4726 | 6 | if (cr != REGVM_OK || ch_val.type != VAL_CHANNEL) { |
4727 | 0 | value_free(&ch_val); |
4728 | 0 | for (uint8_t j = 0; j < i; j++) |
4729 | 0 | if (channels[j]) channel_release(channels[j]); |
4730 | 0 | free(channels); |
4731 | 0 | env_pop_scope(vm->env); |
4732 | 0 | RVM_ERROR("select arm: expression is not a Channel"); |
4733 | 0 | } |
4734 | 6 | channels[i] = ch_val.as.channel.ch; |
4735 | 6 | channel_retain(channels[i]); |
4736 | 6 | value_free(&ch_val); |
4737 | 6 | } |
4738 | | |
4739 | | /* Try non-blocking recv on each channel arm */ |
4740 | 5 | LatValue select_result = value_unit(); |
4741 | 5 | bool select_found = false; |
4742 | 11 | for (uint8_t i = 0; i < arm_count; i++) { |
4743 | 8 | if (sel_arms[i].flags & 0x03) continue; |
4744 | 6 | LatValue recv_val; |
4745 | 6 | bool closed = false; |
4746 | 6 | if (channel_try_recv(channels[i], &recv_val, &closed)) { |
4747 | | /* Got a value — bind in env, run body */ |
4748 | 2 | env_push_scope(vm->env); |
4749 | 2 | if (sel_arms[i].flags & 0x04) { |
4750 | 2 | const char *binding = frame->chunk->constants[sel_arms[i].binding_idx].as.str_val; |
4751 | 2 | if (binding) |
4752 | 2 | env_define(vm->env, binding, recv_val); |
4753 | 0 | else |
4754 | 0 | value_free(&recv_val); |
4755 | 2 | } else { |
4756 | 0 | value_free(&recv_val); |
4757 | 0 | } |
4758 | 2 | RegChunk *body_chunk = (RegChunk *)frame->chunk->constants[sel_arms[i].body_idx].as.closure.native_fn; |
4759 | 2 | LatValue arm_result; |
4760 | 2 | RegVMResult ar = regvm_run_sub(vm, body_chunk, &arm_result); |
4761 | 2 | frame = &vm->frames[vm->frame_count - 1]; |
4762 | 2 | R = frame->regs; |
4763 | 2 | env_pop_scope(vm->env); |
4764 | 2 | if (ar == REGVM_OK) { |
4765 | 2 | value_free(&select_result); |
4766 | 2 | select_result = arm_result; |
4767 | 2 | } |
4768 | 2 | select_found = true; |
4769 | 2 | break; |
4770 | 2 | } |
4771 | 4 | (void)closed; |
4772 | 4 | } |
4773 | | |
4774 | | /* If no channel was ready, execute default arm if present */ |
4775 | 5 | if (!select_found && default_arm >= 0) { |
4776 | 2 | env_push_scope(vm->env); |
4777 | 2 | RegChunk *def_chunk = (RegChunk *)frame->chunk->constants[sel_arms[default_arm].body_idx].as.closure.native_fn; |
4778 | 2 | LatValue def_result; |
4779 | 2 | RegVMResult dr = regvm_run_sub(vm, def_chunk, &def_result); |
4780 | 2 | frame = &vm->frames[vm->frame_count - 1]; |
4781 | 2 | R = frame->regs; |
4782 | 2 | env_pop_scope(vm->env); |
4783 | 2 | if (dr == REGVM_OK) { |
4784 | 2 | value_free(&select_result); |
4785 | 2 | select_result = def_result; |
4786 | 2 | } |
4787 | 2 | } |
4788 | | |
4789 | 14 | for (uint8_t i = 0; i < arm_count; i++) |
4790 | 9 | if (channels[i]) channel_release(channels[i]); |
4791 | 5 | free(channels); |
4792 | 5 | env_pop_scope(vm->env); |
4793 | | |
4794 | 5 | reg_set(&R[dst_reg], select_result); |
4795 | 5 | DISPATCH(); |
4796 | 5 | } |
4797 | | |
4798 | | /* ── Ephemeral arena ── */ |
4799 | | |
4800 | 0 | CASE(RESET_EPHEMERAL) { |
4801 | 0 | if (vm->ephemeral) |
4802 | 0 | bump_arena_reset(vm->ephemeral); |
4803 | 0 | DISPATCH(); |
4804 | 0 | } |
4805 | | |
4806 | | /* ── Optimization opcodes ── */ |
4807 | |
|
4808 | 0 | CASE(ADD_INT) { |
4809 | 0 | uint8_t a = REG_GET_A(instr), b = REG_GET_B(instr), c = REG_GET_C(instr); |
4810 | 0 | R[a].type = VAL_INT; |
4811 | 0 | R[a].as.int_val = R[b].as.int_val + R[c].as.int_val; |
4812 | 0 | DISPATCH(); |
4813 | 0 | } |
4814 | |
|
4815 | 0 | CASE(SUB_INT) { |
4816 | 0 | uint8_t a = REG_GET_A(instr), b = REG_GET_B(instr), c = REG_GET_C(instr); |
4817 | 0 | R[a].type = VAL_INT; |
4818 | 0 | R[a].as.int_val = R[b].as.int_val - R[c].as.int_val; |
4819 | 0 | DISPATCH(); |
4820 | 0 | } |
4821 | |
|
4822 | 0 | CASE(MUL_INT) { |
4823 | 0 | uint8_t a = REG_GET_A(instr), b = REG_GET_B(instr), c = REG_GET_C(instr); |
4824 | 0 | R[a].type = VAL_INT; |
4825 | 0 | R[a].as.int_val = R[b].as.int_val * R[c].as.int_val; |
4826 | 0 | DISPATCH(); |
4827 | 0 | } |
4828 | |
|
4829 | 353 | CASE(LT_INT) { |
4830 | 353 | uint8_t a = REG_GET_A(instr), b = REG_GET_B(instr), c = REG_GET_C(instr); |
4831 | 353 | R[a].type = VAL_BOOL; |
4832 | 353 | R[a].as.bool_val = R[b].as.int_val < R[c].as.int_val; |
4833 | 353 | DISPATCH(); |
4834 | 353 | } |
4835 | | |
4836 | 353 | CASE(LTEQ_INT) { |
4837 | 0 | uint8_t a = REG_GET_A(instr), b = REG_GET_B(instr), c = REG_GET_C(instr); |
4838 | 0 | R[a].type = VAL_BOOL; |
4839 | 0 | R[a].as.bool_val = R[b].as.int_val <= R[c].as.int_val; |
4840 | 0 | DISPATCH(); |
4841 | 0 | } |
4842 | |
|
4843 | 306 | CASE(INC_REG) { |
4844 | 306 | uint8_t a = REG_GET_A(instr); |
4845 | 306 | R[a].as.int_val++; |
4846 | 306 | DISPATCH(); |
4847 | 306 | } |
4848 | | |
4849 | 306 | CASE(DEC_REG) { |
4850 | 0 | uint8_t a = REG_GET_A(instr); |
4851 | 0 | R[a].as.int_val--; |
4852 | 0 | DISPATCH(); |
4853 | 0 | } |
4854 | |
|
4855 | 27 | CASE(SETINDEX_LOCAL) { |
4856 | | /* R[A][R[B]] = R[C] — in-place array/map mutation */ |
4857 | 27 | uint8_t a = REG_GET_A(instr), b = REG_GET_B(instr), c = REG_GET_C(instr); |
4858 | | /* Phase checks */ |
4859 | 27 | if (R[a].phase == VTAG_CRYSTAL) { |
4860 | 1 | bool blocked = true; |
4861 | 1 | if (R[a].type == VAL_MAP && R[b].type == VAL_STR && R[a].as.map.key_phases) { |
4862 | 1 | PhaseTag *kp = lat_map_get(R[a].as.map.key_phases, R[b].as.str_val); |
4863 | 1 | if (!kp || *kp != VTAG_CRYSTAL) blocked = false; |
4864 | 1 | } |
4865 | 1 | if (blocked) |
4866 | 0 | RVM_ERROR("cannot modify a frozen value"); |
4867 | 1 | } |
4868 | 27 | if (R[a].phase == VTAG_SUBLIMATED && R[a].type == VAL_MAP) |
4869 | 1 | RVM_ERROR("cannot add keys to a sublimated map"); |
4870 | 26 | if (R[a].type == VAL_MAP && R[b].type == VAL_STR && R[a].as.map.key_phases) { |
4871 | 3 | PhaseTag *kp = lat_map_get(R[a].as.map.key_phases, R[b].as.str_val); |
4872 | 3 | if (kp && *kp == VTAG_CRYSTAL) |
4873 | 1 | RVM_ERROR("cannot modify frozen key '%s'", R[b].as.str_val); |
4874 | 3 | } |
4875 | 25 | if (R[a].type == VAL_ARRAY) { |
4876 | 2 | if (R[b].type == VAL_INT) { |
4877 | 2 | int64_t idx = R[b].as.int_val; |
4878 | 2 | if (idx < 0) idx += (int64_t)R[a].as.array.len; |
4879 | 2 | if (idx >= 0 && (size_t)idx < R[a].as.array.len) { |
4880 | 2 | value_free(&R[a].as.array.elems[idx]); |
4881 | 2 | R[a].as.array.elems[idx] = rvm_clone(&R[c]); |
4882 | 2 | } |
4883 | 2 | } |
4884 | 23 | } else if (R[a].type == VAL_MAP) { |
4885 | 22 | if (R[b].type == VAL_STR) { |
4886 | 22 | LatValue cloned = rvm_clone(&R[c]); |
4887 | 22 | lat_map_set(R[a].as.map.map, R[b].as.str_val, &cloned); |
4888 | 22 | } |
4889 | 22 | } else if (R[a].type == VAL_REF) { |
4890 | | /* Proxy: set index on inner value */ |
4891 | 1 | LatRef *ref = R[a].as.ref.ref; |
4892 | 1 | if (value_is_crystal(&R[a])) |
4893 | 0 | RVM_ERROR("cannot mutate a frozen Ref"); |
4894 | 1 | if (ref->value.type == VAL_MAP) { |
4895 | 1 | if (R[b].type == VAL_STR) { |
4896 | 1 | LatValue cloned = rvm_clone(&R[c]); |
4897 | 1 | lat_map_set(ref->value.as.map.map, R[b].as.str_val, &cloned); |
4898 | 1 | } |
4899 | 1 | } else if (ref->value.type == VAL_ARRAY) { |
4900 | 0 | if (R[b].type == VAL_INT) { |
4901 | 0 | int64_t idx = R[b].as.int_val; |
4902 | 0 | if (idx < 0) idx += (int64_t)ref->value.as.array.len; |
4903 | 0 | if (idx >= 0 && (size_t)idx < ref->value.as.array.len) { |
4904 | 0 | value_free(&ref->value.as.array.elems[idx]); |
4905 | 0 | ref->value.as.array.elems[idx] = rvm_clone(&R[c]); |
4906 | 0 | } |
4907 | 0 | } |
4908 | 0 | } |
4909 | 1 | } |
4910 | 25 | DISPATCH(); |
4911 | 25 | } |
4912 | | |
4913 | 209 | CASE(INVOKE_GLOBAL) { |
4914 | | /* Two-instruction sequence: |
4915 | | * INVOKE_GLOBAL dst, name_ki, argc |
4916 | | * data: method_ki, args_base, 0 |
4917 | | * Mutates the global value in-place (for push/pop/etc). */ |
4918 | 209 | uint8_t dst = REG_GET_A(instr); |
4919 | 209 | uint8_t name_ki = REG_GET_B(instr); |
4920 | 209 | uint8_t argc = REG_GET_C(instr); |
4921 | | |
4922 | 209 | RegInstr data = *frame->ip++; |
4923 | 209 | uint8_t method_ki = REG_GET_A(data); |
4924 | 209 | uint8_t args_base = REG_GET_B(data); |
4925 | | |
4926 | 209 | const char *global_name = frame->chunk->constants[name_ki].as.str_val; |
4927 | 209 | const char *method_name = frame->chunk->constants[method_ki].as.str_val; |
4928 | | |
4929 | | /* Get a direct reference to the global value */ |
4930 | 209 | LatValue *obj_ref = env_get_ref(vm->env, global_name); |
4931 | 209 | if (!obj_ref) { |
4932 | 0 | RVM_ERROR("undefined variable '%s'", global_name); |
4933 | 0 | DISPATCH(); |
4934 | 0 | } |
4935 | | |
4936 | | /* Try builtin — mutates obj_ref in-place */ |
4937 | 209 | LatValue invoke_result; |
4938 | 209 | LatValue *invoke_args = (argc > 0) ? &R[args_base] : NULL; |
4939 | 209 | if (rvm_invoke_builtin(vm, obj_ref, method_name, invoke_args, argc, &invoke_result)) { |
4940 | 41 | if (vm->error) |
4941 | 0 | return REGVM_RUNTIME_ERROR; |
4942 | 41 | reg_set(&R[dst], invoke_result); |
4943 | 41 | DISPATCH(); |
4944 | 41 | } |
4945 | | |
4946 | | /* Check for callable closure field in struct */ |
4947 | 209 | if (obj_ref->type == VAL_STRUCT) { |
4948 | 0 | for (size_t fi = 0; fi < obj_ref->as.strct.field_count; fi++) { |
4949 | 0 | if (strcmp(obj_ref->as.strct.field_names[fi], method_name) != 0) |
4950 | 0 | continue; |
4951 | 0 | LatValue *field = &obj_ref->as.strct.field_values[fi]; |
4952 | 0 | if (field->type == VAL_CLOSURE) { |
4953 | | /* Copy object into a temp register, then proceed like normal INVOKE */ |
4954 | 0 | uint8_t tmp = args_base > 0 ? args_base - 1 : dst; |
4955 | 0 | reg_set(&R[tmp], rvm_clone(obj_ref)); |
4956 | | /* Fall through to closure call logic similar to INVOKE */ |
4957 | 0 | LatValue closure = rvm_clone(field); |
4958 | 0 | if (closure.as.closure.body == NULL && |
4959 | 0 | closure.as.closure.native_fn != NULL && |
4960 | 0 | closure.as.closure.default_values != VM_NATIVE_MARKER && |
4961 | 0 | closure.as.closure.default_values != VM_EXT_MARKER) { |
4962 | | /* Compiled closure */ |
4963 | 0 | RegChunk *fn_chunk = (RegChunk *)closure.as.closure.native_fn; |
4964 | 0 | if (vm->frame_count >= REGVM_FRAMES_MAX) { |
4965 | 0 | value_free(&closure); |
4966 | 0 | RVM_ERROR("stack overflow"); |
4967 | 0 | DISPATCH(); |
4968 | 0 | } |
4969 | 0 | LatValue *new_regs = &vm->reg_stack[vm->reg_stack_top]; |
4970 | 0 | vm->reg_stack_top += REGVM_REG_MAX; |
4971 | 0 | for (int ri = 0; ri < REGVM_REG_MAX; ri++) |
4972 | 0 | new_regs[ri] = value_nil(); |
4973 | | |
4974 | | /* Slot 0 = reserved, slot 1 = self, slots 2+ = args */ |
4975 | 0 | new_regs[0] = value_unit(); |
4976 | 0 | new_regs[1] = rvm_clone(obj_ref); /* self = first param */ |
4977 | 0 | value_free(&closure); |
4978 | | /* Copy args into param slots */ |
4979 | 0 | for (int ai = 0; ai < argc && ai + 2 < REGVM_REG_MAX; ai++) |
4980 | 0 | new_regs[ai + 2] = rvm_clone(&R[args_base + ai]); |
4981 | |
|
4982 | 0 | RegCallFrame *nf = &vm->frames[vm->frame_count++]; |
4983 | 0 | nf->chunk = fn_chunk; |
4984 | 0 | nf->ip = fn_chunk->code; |
4985 | 0 | nf->regs = new_regs; |
4986 | 0 | nf->reg_count = REGVM_REG_MAX; |
4987 | 0 | nf->caller_result_reg = dst; |
4988 | |
|
4989 | 0 | ObjUpvalue **upvals = (ObjUpvalue **)closure.as.closure.captured_env; |
4990 | 0 | size_t uv_count = closure.region_id; |
4991 | 0 | nf->upvalues = upvals; |
4992 | 0 | nf->upvalue_count = uv_count; |
4993 | |
|
4994 | 0 | frame = nf; |
4995 | 0 | R = frame->regs; |
4996 | 0 | DISPATCH(); |
4997 | 0 | } |
4998 | 0 | value_free(&closure); |
4999 | 0 | } |
5000 | 0 | break; |
5001 | 0 | } |
5002 | 0 | } |
5003 | | |
5004 | | /* Check for callable closure field in map (global) */ |
5005 | 209 | if (obj_ref->type == VAL_MAP) { |
5006 | 168 | LatValue *field = lat_map_get(obj_ref->as.map.map, method_name); |
5007 | 168 | if (field && field->type == VAL_CLOSURE) { |
5008 | 168 | if (field->as.closure.default_values == VM_NATIVE_MARKER) { |
5009 | 2 | VMNativeFn native = (VMNativeFn)field->as.closure.native_fn; |
5010 | 2 | LatValue *call_args = (argc > 0) ? &R[args_base] : NULL; |
5011 | 2 | LatValue ret = native(call_args, argc); |
5012 | 2 | if (vm->rt->error) { |
5013 | 0 | vm->error = vm->rt->error; |
5014 | 0 | vm->rt->error = NULL; |
5015 | 0 | value_free(&ret); |
5016 | 0 | return REGVM_RUNTIME_ERROR; |
5017 | 0 | } |
5018 | 2 | reg_set(&R[dst], ret); |
5019 | 2 | DISPATCH(); |
5020 | 2 | } |
5021 | 168 | if (field->as.closure.default_values == VM_EXT_MARKER) { |
5022 | 0 | LatValue *call_args = (argc > 0) ? &R[args_base] : NULL; |
5023 | 0 | LatValue ret = ext_call_native(field->as.closure.native_fn, |
5024 | 0 | call_args, (size_t)argc); |
5025 | 0 | if (ret.type == VAL_STR && ret.as.str_val && |
5026 | 0 | strncmp(ret.as.str_val, "EVAL_ERROR:", 11) == 0) { |
5027 | 0 | vm->error = strdup(ret.as.str_val + 11); |
5028 | 0 | value_free(&ret); |
5029 | 0 | return REGVM_RUNTIME_ERROR; |
5030 | 0 | } |
5031 | 0 | reg_set(&R[dst], ret); |
5032 | 0 | DISPATCH(); |
5033 | 0 | } |
5034 | 168 | RegChunk *fn_chunk = (RegChunk *)field->as.closure.native_fn; |
5035 | 168 | if (fn_chunk) { |
5036 | 166 | uint32_t magic; |
5037 | 166 | memcpy(&magic, fn_chunk, sizeof(uint32_t)); |
5038 | 166 | if (magic == REGCHUNK_MAGIC) { |
5039 | 166 | if (vm->frame_count >= REGVM_FRAMES_MAX) |
5040 | 0 | RVM_ERROR("call stack overflow"); |
5041 | 166 | LatValue *new_regs = &vm->reg_stack[vm->reg_stack_top]; |
5042 | 166 | vm->reg_stack_top += REGVM_REG_MAX; |
5043 | 42.6k | for (int ri = 0; ri < REGVM_REG_MAX; ri++) |
5044 | 42.4k | new_regs[ri] = value_nil(); |
5045 | 166 | new_regs[0] = value_unit(); |
5046 | 410 | for (int ai = 0; ai < argc; ai++) |
5047 | 244 | new_regs[1 + ai] = rvm_clone(&R[args_base + ai]); |
5048 | | |
5049 | 166 | ObjUpvalue **upvals = (ObjUpvalue **)field->as.closure.captured_env; |
5050 | 166 | size_t uv_count = field->region_id != (size_t)-1 ? field->region_id : 0; |
5051 | | |
5052 | 166 | RegCallFrame *nf = &vm->frames[vm->frame_count++]; |
5053 | 166 | nf->chunk = fn_chunk; |
5054 | 166 | nf->ip = fn_chunk->code; |
5055 | 166 | nf->regs = new_regs; |
5056 | 166 | nf->reg_count = REGVM_REG_MAX; |
5057 | 166 | nf->upvalues = upvals; |
5058 | 166 | nf->upvalue_count = uv_count; |
5059 | 166 | nf->caller_result_reg = dst; |
5060 | 166 | frame = nf; |
5061 | 166 | R = frame->regs; |
5062 | 166 | DISPATCH(); |
5063 | 166 | } |
5064 | 166 | } |
5065 | 168 | } |
5066 | 168 | } |
5067 | | |
5068 | | /* Fallback: copy global into temp, do regular invoke */ |
5069 | 209 | { |
5070 | 209 | LatValue obj_copy = rvm_clone(obj_ref); |
5071 | 209 | LatValue fb_result; |
5072 | 209 | LatValue *fb_args = (argc > 0) ? &R[args_base] : NULL; |
5073 | 209 | if (rvm_invoke_builtin(vm, &obj_copy, method_name, fb_args, argc, &fb_result)) { |
5074 | | /* Write back the mutated copy to the global */ |
5075 | 0 | value_free(obj_ref); |
5076 | 0 | *obj_ref = obj_copy; |
5077 | 0 | reg_set(&R[dst], fb_result); |
5078 | 209 | } else { |
5079 | 209 | value_free(&obj_copy); |
5080 | 209 | reg_set(&R[dst], value_nil()); |
5081 | 209 | } |
5082 | 209 | } |
5083 | 209 | DISPATCH(); |
5084 | 209 | } |
5085 | | |
5086 | 16 | CASE(IS_CRYSTAL) { |
5087 | 16 | uint8_t a = REG_GET_A(instr); |
5088 | 16 | uint8_t b = REG_GET_B(instr); |
5089 | 16 | reg_set(&R[a], value_bool(R[b].phase == VTAG_CRYSTAL)); |
5090 | 16 | DISPATCH(); |
5091 | 16 | } |
5092 | | |
5093 | 900 | CASE(CHECK_TYPE) { |
5094 | | /* 2-word instruction: |
5095 | | * word 1: A=value reg, Bx=expected type name constant index |
5096 | | * word 2: error message constant index (raw 32-bit, 0xFFFFFFFF = default) */ |
5097 | 900 | uint8_t a = REG_GET_A(instr); |
5098 | 900 | uint16_t bx = REG_GET_Bx(instr); |
5099 | 900 | uint32_t err_word = *frame->ip++; |
5100 | 900 | const char *expected = frame->chunk->constants[bx].as.str_val; |
5101 | | /* Type matching logic (mirrors vm_type_matches from stack VM) */ |
5102 | 900 | bool type_ok = false; |
5103 | 900 | if (!expected || strcmp(expected, "Any") == 0 || strcmp(expected, "any") == 0) { |
5104 | 7 | type_ok = true; |
5105 | 893 | } else if (strcmp(expected, "Int") == 0) { |
5106 | 126 | type_ok = R[a].type == VAL_INT; |
5107 | 767 | } else if (strcmp(expected, "Float") == 0) { |
5108 | 0 | type_ok = R[a].type == VAL_FLOAT; |
5109 | 767 | } else if (strcmp(expected, "String") == 0) { |
5110 | 73 | type_ok = R[a].type == VAL_STR; |
5111 | 694 | } else if (strcmp(expected, "Bool") == 0) { |
5112 | 27 | type_ok = R[a].type == VAL_BOOL; |
5113 | 667 | } else if (strcmp(expected, "Nil") == 0) { |
5114 | 0 | type_ok = R[a].type == VAL_NIL; |
5115 | 667 | } else if (strcmp(expected, "Map") == 0) { |
5116 | 537 | type_ok = R[a].type == VAL_MAP; |
5117 | 537 | } else if (strcmp(expected, "Array") == 0) { |
5118 | 62 | type_ok = R[a].type == VAL_ARRAY; |
5119 | 68 | } else if (strcmp(expected, "Fn") == 0 || strcmp(expected, "Closure") == 0) { |
5120 | 57 | type_ok = R[a].type == VAL_CLOSURE; |
5121 | 57 | } else if (strcmp(expected, "Channel") == 0) { |
5122 | 0 | type_ok = R[a].type == VAL_CHANNEL; |
5123 | 11 | } else if (strcmp(expected, "Range") == 0) { |
5124 | 0 | type_ok = R[a].type == VAL_RANGE; |
5125 | 11 | } else if (strcmp(expected, "Set") == 0) { |
5126 | 0 | type_ok = R[a].type == VAL_SET; |
5127 | 11 | } else if (strcmp(expected, "Tuple") == 0) { |
5128 | 0 | type_ok = R[a].type == VAL_TUPLE; |
5129 | 11 | } else if (strcmp(expected, "Buffer") == 0) { |
5130 | 0 | type_ok = R[a].type == VAL_BUFFER; |
5131 | 11 | } else if (strcmp(expected, "Ref") == 0) { |
5132 | 0 | type_ok = R[a].type == VAL_REF; |
5133 | 11 | } else if (strcmp(expected, "Number") == 0) { |
5134 | 5 | type_ok = R[a].type == VAL_INT || R[a].type == VAL_FLOAT; |
5135 | 6 | } else if (R[a].type == VAL_STRUCT && R[a].as.strct.name) { |
5136 | 5 | type_ok = strcmp(R[a].as.strct.name, expected) == 0; |
5137 | 5 | } else if (R[a].type == VAL_ENUM && R[a].as.enm.enum_name) { |
5138 | 1 | type_ok = strcmp(R[a].as.enm.enum_name, expected) == 0; |
5139 | 1 | } |
5140 | 900 | if (!type_ok) { |
5141 | 4 | const char *display; |
5142 | 4 | if (R[a].type == VAL_STRUCT && R[a].as.strct.name) |
5143 | 1 | display = R[a].as.strct.name; |
5144 | 3 | else if (R[a].type == VAL_ENUM && R[a].as.enm.enum_name) |
5145 | 0 | display = R[a].as.enm.enum_name; |
5146 | 3 | else if (R[a].type == VAL_CLOSURE) |
5147 | 0 | display = "Fn"; |
5148 | 3 | else |
5149 | 3 | display = value_type_name(&R[a]); |
5150 | 4 | if (err_word != 0xFFFFFFFF) { |
5151 | | /* Custom error format with %s placeholder for actual type */ |
5152 | 4 | const char *fmt = frame->chunk->constants[err_word].as.str_val; |
5153 | 4 | RVM_ERROR(fmt, display); |
5154 | 4 | } else { |
5155 | 0 | RVM_ERROR("return type expects %s, got %s", expected, display); |
5156 | 0 | } |
5157 | 4 | } |
5158 | 896 | DISPATCH(); |
5159 | 896 | } |
5160 | | |
5161 | 4 | CASE(FREEZE_FIELD) { |
5162 | | /* A=var reg, B=field name constant */ |
5163 | 4 | uint8_t a = REG_GET_A(instr); |
5164 | 4 | uint8_t b_ki = REG_GET_B(instr); |
5165 | 4 | const char *field_name = frame->chunk->constants[b_ki].as.str_val; |
5166 | | |
5167 | 4 | if (R[a].type == VAL_STRUCT) { |
5168 | 2 | size_t fi = (size_t)-1; |
5169 | 2 | for (size_t i = 0; i < R[a].as.strct.field_count; i++) { |
5170 | 2 | if (strcmp(R[a].as.strct.field_names[i], field_name) == 0) { fi = i; break; } |
5171 | 2 | } |
5172 | 2 | if (fi == (size_t)-1) RVM_ERROR("struct has no field '%s'", field_name); |
5173 | 2 | R[a].as.strct.field_values[fi] = value_freeze(R[a].as.strct.field_values[fi]); |
5174 | 2 | if (!R[a].as.strct.field_phases) |
5175 | 2 | R[a].as.strct.field_phases = calloc(R[a].as.strct.field_count, sizeof(PhaseTag)); |
5176 | 2 | R[a].as.strct.field_phases[fi] = VTAG_CRYSTAL; |
5177 | 2 | } else if (R[a].type == VAL_MAP) { |
5178 | 2 | LatValue *val_ptr = (LatValue *)lat_map_get(R[a].as.map.map, field_name); |
5179 | 2 | if (val_ptr) *val_ptr = value_freeze(*val_ptr); |
5180 | 2 | if (!R[a].as.map.key_phases) { |
5181 | 2 | R[a].as.map.key_phases = calloc(1, sizeof(LatMap)); |
5182 | 2 | *R[a].as.map.key_phases = lat_map_new(sizeof(PhaseTag)); |
5183 | 2 | } |
5184 | 2 | PhaseTag phase = VTAG_CRYSTAL; |
5185 | 2 | lat_map_set(R[a].as.map.key_phases, field_name, &phase); |
5186 | 2 | } |
5187 | 4 | DISPATCH(); |
5188 | 4 | } |
5189 | | |
5190 | 3 | CASE(THAW_FIELD) { |
5191 | | /* A=var reg, B=field name constant */ |
5192 | 3 | uint8_t a = REG_GET_A(instr); |
5193 | 3 | uint8_t b_ki = REG_GET_B(instr); |
5194 | 3 | const char *field_name = frame->chunk->constants[b_ki].as.str_val; |
5195 | | |
5196 | 3 | if (R[a].type == VAL_STRUCT) { |
5197 | 2 | if (!R[a].as.strct.field_phases) { |
5198 | 2 | R[a].as.strct.field_phases = calloc(R[a].as.strct.field_count, sizeof(PhaseTag)); |
5199 | 8 | for (size_t i = 0; i < R[a].as.strct.field_count; i++) |
5200 | 6 | R[a].as.strct.field_phases[i] = R[a].phase; |
5201 | 2 | } |
5202 | 6 | for (size_t i = 0; i < R[a].as.strct.field_count; i++) { |
5203 | 6 | if (strcmp(R[a].as.strct.field_names[i], field_name) == 0) { |
5204 | 2 | R[a].as.strct.field_phases[i] = VTAG_FLUID; |
5205 | 2 | break; |
5206 | 2 | } |
5207 | 6 | } |
5208 | 2 | } else if (R[a].type == VAL_MAP) { |
5209 | 1 | if (!R[a].as.map.key_phases) { |
5210 | 1 | R[a].as.map.key_phases = calloc(1, sizeof(LatMap)); |
5211 | 1 | *R[a].as.map.key_phases = lat_map_new(sizeof(PhaseTag)); |
5212 | 1 | } |
5213 | 1 | PhaseTag phase = VTAG_FLUID; |
5214 | 1 | lat_map_set(R[a].as.map.key_phases, field_name, &phase); |
5215 | 1 | } |
5216 | 3 | DISPATCH(); |
5217 | 3 | } |
5218 | | |
5219 | 8 | CASE(HALT) { |
5220 | 8 | *result = value_unit(); |
5221 | 8 | return REGVM_OK; |
5222 | 3 | } |
5223 | | |
5224 | 0 | #ifdef VM_USE_COMPUTED_GOTO |
5225 | | /* End of computed goto block - should never reach here */ |
5226 | 0 | RVM_ERROR("unreachable: fell through computed goto dispatch"); |
5227 | | #else |
5228 | | default: |
5229 | | RVM_ERROR("unknown register opcode %d", REG_GET_OP(instr)); |
5230 | | } /* end switch */ |
5231 | | } /* end for */ |
5232 | | #endif |
5233 | 0 | } |
5234 | | |
5235 | | /* ── Dispatch adapters for LatRuntime ── */ |
5236 | | |
5237 | | /* call_closure: delegates to the register VM's closure call */ |
5238 | 19 | static LatValue regvm_dispatch_call_closure(void *opaque_vm, LatValue *closure, LatValue *args, int argc) { |
5239 | 19 | return regvm_call_closure((RegVM *)opaque_vm, closure, args, argc); |
5240 | 19 | } |
5241 | | |
5242 | | /* find_local_value: searches register frame locals for a named variable */ |
5243 | 1 | static bool regvm_dispatch_find_local_value(void *opaque_vm, const char *name, LatValue *out) { |
5244 | 1 | RegVM *rvm = (RegVM *)opaque_vm; |
5245 | 3 | for (int fi = 0; fi < rvm->frame_count; fi++) { |
5246 | 2 | RegCallFrame *f = &rvm->frames[fi]; |
5247 | 2 | if (!f->chunk || !f->chunk->local_names) continue; |
5248 | 0 | for (size_t r = 0; r < f->chunk->local_name_cap; r++) { |
5249 | 0 | if (f->chunk->local_names[r] && strcmp(f->chunk->local_names[r], name) == 0) { |
5250 | 0 | *out = value_deep_clone(&f->regs[r]); |
5251 | 0 | return true; |
5252 | 0 | } |
5253 | 0 | } |
5254 | 0 | } |
5255 | 1 | return false; |
5256 | 1 | } |
5257 | | |
5258 | | /* current_line: returns the source line of the current instruction */ |
5259 | 30 | static int regvm_dispatch_current_line(void *opaque_vm) { |
5260 | 30 | RegVM *rvm = (RegVM *)opaque_vm; |
5261 | 30 | if (rvm->frame_count <= 0) return 0; |
5262 | 30 | RegCallFrame *f = &rvm->frames[rvm->frame_count - 1]; |
5263 | 30 | if (f->ip > f->chunk->code) { |
5264 | 30 | size_t offset = (size_t)(f->ip - f->chunk->code - 1); |
5265 | 30 | if (offset < f->chunk->lines_len) |
5266 | 30 | return f->chunk->lines[offset]; |
5267 | 30 | } |
5268 | 0 | return 0; |
5269 | 30 | } |
5270 | | |
5271 | | /* get_var_by_name: searches locals then globals */ |
5272 | 21 | static bool regvm_dispatch_get_var_by_name(void *opaque_vm, const char *name, LatValue *out) { |
5273 | 21 | RegVM *rvm = (RegVM *)opaque_vm; |
5274 | | /* Search frame locals first */ |
5275 | 42 | for (int fi = 0; fi < rvm->frame_count; fi++) { |
5276 | 42 | RegCallFrame *f = &rvm->frames[fi]; |
5277 | 42 | if (!f->chunk || !f->chunk->local_names) continue; |
5278 | 59 | for (size_t r = 0; r < f->chunk->local_name_cap; r++) { |
5279 | 59 | if (f->chunk->local_names[r] && strcmp(f->chunk->local_names[r], name) == 0) { |
5280 | 21 | *out = value_deep_clone(&f->regs[r]); |
5281 | 21 | return true; |
5282 | 21 | } |
5283 | 59 | } |
5284 | 21 | } |
5285 | | /* Fall back to globals */ |
5286 | 0 | return env_get(rvm->env, name, out); |
5287 | 21 | } |
5288 | | |
5289 | | /* set_var_by_name: sets in locals (if found) or globals */ |
5290 | 11 | static bool regvm_dispatch_set_var_by_name(void *opaque_vm, const char *name, LatValue val) { |
5291 | 11 | RegVM *rvm = (RegVM *)opaque_vm; |
5292 | | /* Try frame locals first */ |
5293 | 22 | for (int fi = 0; fi < rvm->frame_count; fi++) { |
5294 | 22 | RegCallFrame *f = &rvm->frames[fi]; |
5295 | 22 | if (!f->chunk || !f->chunk->local_names) continue; |
5296 | 36 | for (size_t r = 0; r < f->chunk->local_name_cap; r++) { |
5297 | 36 | if (f->chunk->local_names[r] && strcmp(f->chunk->local_names[r], name) == 0) { |
5298 | 11 | value_free(&f->regs[r]); |
5299 | 11 | f->regs[r] = val; |
5300 | | /* Also sync to env */ |
5301 | 11 | LatValue clone = value_deep_clone(&val); |
5302 | 11 | if (!env_set(rvm->env, name, clone)) |
5303 | 9 | env_define(rvm->env, name, clone); |
5304 | 11 | return true; |
5305 | 11 | } |
5306 | 36 | } |
5307 | 11 | } |
5308 | | /* Fall back to globals */ |
5309 | 0 | if (env_set(rvm->env, name, val)) |
5310 | 0 | return true; |
5311 | 0 | env_define(rvm->env, name, val); |
5312 | 0 | return true; |
5313 | 0 | } |
5314 | | |
5315 | | /* Set up runtime dispatch pointers for the register VM */ |
5316 | 857 | static void regvm_setup_dispatch(RegVM *vm) { |
5317 | 857 | vm->rt->backend = RT_BACKEND_REG_VM; |
5318 | 857 | vm->rt->active_vm = vm; |
5319 | 857 | vm->rt->call_closure = regvm_dispatch_call_closure; |
5320 | 857 | vm->rt->find_local_value = regvm_dispatch_find_local_value; |
5321 | 857 | vm->rt->current_line = regvm_dispatch_current_line; |
5322 | 857 | vm->rt->get_var_by_name = regvm_dispatch_get_var_by_name; |
5323 | 857 | vm->rt->set_var_by_name = regvm_dispatch_set_var_by_name; |
5324 | 857 | lat_runtime_set_current(vm->rt); |
5325 | 857 | } |
5326 | | |
5327 | 857 | RegVMResult regvm_run(RegVM *vm, RegChunk *chunk, LatValue *result) { |
5328 | | /* Set up runtime dispatch so native functions can call back into regvm */ |
5329 | 857 | regvm_setup_dispatch(vm); |
5330 | | |
5331 | | /* Reentrant: push a new frame on top of the existing stack. |
5332 | | * This is safe for recursive calls (e.g. native_lat_eval, native_require) |
5333 | | * because we don't clobber existing frames. */ |
5334 | 857 | int base_frame = vm->frame_count; |
5335 | 857 | if (vm->frame_count >= REGVM_FRAMES_MAX) { |
5336 | 0 | vm->error = strdup("regvm_run: frame overflow"); |
5337 | 0 | return REGVM_RUNTIME_ERROR; |
5338 | 0 | } |
5339 | 857 | RegCallFrame *frame = &vm->frames[vm->frame_count++]; |
5340 | 857 | frame->chunk = chunk; |
5341 | 857 | frame->ip = chunk->code; |
5342 | 857 | frame->regs = &vm->reg_stack[vm->reg_stack_top]; |
5343 | 857 | frame->reg_count = REGVM_REG_MAX; |
5344 | 857 | frame->upvalues = NULL; |
5345 | 857 | frame->upvalue_count = 0; |
5346 | 857 | frame->caller_result_reg = 0; |
5347 | 857 | vm->reg_stack_top += REGVM_REG_MAX; |
5348 | | |
5349 | | /* Zero the new register window */ |
5350 | 857 | memset(frame->regs, 0, REGVM_REG_MAX * sizeof(LatValue)); |
5351 | | |
5352 | 857 | return regvm_dispatch(vm, base_frame, result); |
5353 | 857 | } |
5354 | | |
5355 | | /* REPL variant: reuses existing frame 0 registers (preserves globals/locals) */ |
5356 | 0 | RegVMResult regvm_run_repl(RegVM *vm, RegChunk *chunk, LatValue *result) { |
5357 | | /* Set up runtime dispatch so native functions can call back into regvm */ |
5358 | 0 | regvm_setup_dispatch(vm); |
5359 | 0 | RegCallFrame *frame = &vm->frames[0]; |
5360 | 0 | frame->chunk = chunk; |
5361 | 0 | frame->ip = chunk->code; |
5362 | | /* Reuse existing regs — don't reset reg_stack */ |
5363 | 0 | frame->regs = vm->reg_stack; |
5364 | 0 | frame->reg_count = REGVM_REG_MAX; |
5365 | | /* Preserve upvalues from previous iterations */ |
5366 | 0 | frame->caller_result_reg = 0; |
5367 | 0 | vm->frame_count = 1; |
5368 | 0 | vm->reg_stack_top = REGVM_REG_MAX; |
5369 | |
|
5370 | 0 | return regvm_dispatch(vm, 0, result); |
5371 | 0 | } |