/Users/alexjokela/projects/lattice/src/runtime.c
Line | Count | Source |
1 | | #include "runtime.h" |
2 | | #include "stackvm.h" /* For StackVM, StackCallFrame, ObjUpvalue, stackvm_run, stackvm_track_chunk, Chunk types */ |
3 | | #include "regvm.h" /* For RegVM, RegChunk, regvm_run, regvm_track_chunk */ |
4 | | #include "stackopcode.h" |
5 | | #include "stackcompiler.h" |
6 | | #include "intern.h" |
7 | | #include "builtins.h" |
8 | | #include "lattice.h" |
9 | | #include "math_ops.h" |
10 | | #include "fs_ops.h" |
11 | | #include "path_ops.h" |
12 | | #include "net.h" |
13 | | #include "tls.h" |
14 | | #include "http.h" |
15 | | #include "json.h" |
16 | | #include "toml_ops.h" |
17 | | #include "yaml_ops.h" |
18 | | #include "crypto_ops.h" |
19 | | #include "regex_ops.h" |
20 | | #include "time_ops.h" |
21 | | #include "datetime_ops.h" |
22 | | #include "env_ops.h" |
23 | | #include "process_ops.h" |
24 | | #include "format_ops.h" |
25 | | #include "type_ops.h" |
26 | | #include "string_ops.h" |
27 | | #include "array_ops.h" |
28 | | #include "channel.h" |
29 | | #include "ext.h" |
30 | | #include "lexer.h" |
31 | | #include "parser.h" |
32 | | #include "stackcompiler.h" |
33 | | #include "latc.h" |
34 | | #include <stdlib.h> |
35 | | #include <limits.h> |
36 | | #include <libgen.h> |
37 | | #include <string.h> |
38 | | #include <stdio.h> |
39 | | #include <stdarg.h> |
40 | | #ifndef __EMSCRIPTEN__ |
41 | | #include <pthread.h> |
42 | | #endif |
43 | | #include "memory.h" |
44 | | |
45 | | /* Native function pointer for StackVM builtins. */ |
46 | | typedef LatValue (*VMNativeFn)(LatValue *args, int arg_count); |
47 | | |
48 | | /* Sentinel to distinguish native C functions from compiled closures. */ |
49 | 276k | #define VM_NATIVE_MARKER ((struct Expr **)(uintptr_t)0x1) |
50 | 180 | #define VM_EXT_MARKER ((struct Expr **)(uintptr_t)0x2) |
51 | | |
52 | | /* Thread-local runtime pointer for native function dispatch. */ |
53 | | static _Thread_local LatRuntime *current_rt = NULL; |
54 | | |
55 | 3.85k | void lat_runtime_set_current(LatRuntime *rt) { current_rt = rt; } |
56 | 1.72k | LatRuntime *lat_runtime_current(void) { return current_rt; } |
57 | | |
58 | | |
59 | | /* ── Phase system functions ── */ |
60 | | |
61 | 458 | void rt_record_history(LatRuntime *rt, const char *name, LatValue *val) { |
62 | 481 | for (size_t i = 0; i < rt->tracked_count; i++) { |
63 | 83 | if (strcmp(rt->tracked_vars[i].name, name) != 0) continue; |
64 | 60 | if (rt->tracked_vars[i].snap_count >= rt->tracked_vars[i].snap_cap) { |
65 | 26 | rt->tracked_vars[i].snap_cap = rt->tracked_vars[i].snap_cap ? rt->tracked_vars[i].snap_cap * 2 : 4; |
66 | 26 | rt->tracked_vars[i].snapshots = realloc(rt->tracked_vars[i].snapshots, |
67 | 26 | rt->tracked_vars[i].snap_cap * sizeof(*rt->tracked_vars[i].snapshots)); |
68 | 26 | } |
69 | 60 | size_t si = rt->tracked_vars[i].snap_count++; |
70 | 60 | const char *phase_name = builtin_phase_of_str(val); |
71 | 60 | rt->tracked_vars[i].snapshots[si].phase = strdup(phase_name); |
72 | 60 | rt->tracked_vars[i].snapshots[si].value = value_deep_clone(val); |
73 | 60 | rt->tracked_vars[i].snapshots[si].line = rt->current_line ? rt->current_line(rt->active_vm) : 0; |
74 | 60 | rt->tracked_vars[i].snapshots[si].fn_name = NULL; |
75 | 60 | return; |
76 | 83 | } |
77 | 458 | } |
78 | | |
79 | 763 | void rt_fire_reactions(LatRuntime *rt, const char *name, const char *phase) { |
80 | 765 | for (size_t i = 0; i < rt->reaction_count; i++) { |
81 | 18 | if (strcmp(rt->reactions[i].var_name, name) != 0) continue; |
82 | 16 | LatValue cur; |
83 | 16 | bool found = rt->get_var_by_name(rt->active_vm, name, &cur); |
84 | 16 | if (!found) return; |
85 | 32 | for (size_t j = 0; j < rt->reactions[i].cb_count; j++) { |
86 | 18 | LatValue *cb = &rt->reactions[i].callbacks[j]; |
87 | 18 | LatValue args[2]; |
88 | 18 | args[0] = value_string(phase); |
89 | 18 | args[1] = value_deep_clone(&cur); |
90 | 18 | LatValue result = rt->call_closure(rt->active_vm, cb, args, 2); |
91 | 18 | value_free(&args[0]); |
92 | 18 | value_free(&args[1]); |
93 | 18 | value_free(&result); |
94 | 18 | if (rt->error) { |
95 | 2 | char *wrapped = NULL; |
96 | 2 | (void)asprintf(&wrapped, "reaction error: %s", rt->error); |
97 | 2 | free(rt->error); |
98 | 2 | rt->error = wrapped; |
99 | 2 | value_free(&cur); |
100 | 2 | return; |
101 | 2 | } |
102 | 18 | } |
103 | 14 | value_free(&cur); |
104 | 14 | return; |
105 | 16 | } |
106 | 763 | } |
107 | | |
108 | 423 | void rt_freeze_cascade(LatRuntime *rt, const char *target_name) { |
109 | 449 | for (size_t bi = 0; bi < rt->bond_count; bi++) { |
110 | 48 | if (strcmp(rt->bonds[bi].target, target_name) != 0) continue; |
111 | 46 | for (size_t di = 0; di < rt->bonds[bi].dep_count; di++) { |
112 | 26 | const char *dep = rt->bonds[bi].deps[di]; |
113 | 26 | const char *strategy = rt->bonds[bi].dep_strategies ? rt->bonds[bi].dep_strategies[di] : "mirror"; |
114 | 26 | LatValue dval; |
115 | 26 | if (!rt->get_var_by_name(rt->active_vm, dep, &dval)) continue; |
116 | 26 | if (dval.type == VAL_CHANNEL) { value_free(&dval); continue; } |
117 | | |
118 | 26 | if (strcmp(strategy, "mirror") == 0) { |
119 | 20 | if (dval.phase == VTAG_CRYSTAL) { value_free(&dval); continue; } |
120 | 20 | LatValue frozen = value_freeze(dval); |
121 | 20 | rt->set_var_by_name(rt->active_vm, dep, value_deep_clone(&frozen)); |
122 | 20 | value_free(&frozen); |
123 | 20 | rt_fire_reactions(rt, dep, "crystal"); |
124 | 20 | if (rt->error) return; |
125 | 20 | rt_freeze_cascade(rt, dep); |
126 | 20 | if (rt->error) return; |
127 | 20 | } else if (strcmp(strategy, "inverse") == 0) { |
128 | 2 | if (dval.phase != VTAG_CRYSTAL && dval.phase != VTAG_SUBLIMATED) { value_free(&dval); continue; } |
129 | 2 | LatValue thawed = value_thaw(&dval); |
130 | 2 | value_free(&dval); |
131 | 2 | rt->set_var_by_name(rt->active_vm, dep, value_deep_clone(&thawed)); |
132 | 2 | value_free(&thawed); |
133 | 2 | rt_fire_reactions(rt, dep, "fluid"); |
134 | 2 | if (rt->error) return; |
135 | 4 | } else if (strcmp(strategy, "gate") == 0) { |
136 | 4 | if (dval.phase != VTAG_CRYSTAL) { |
137 | 2 | value_free(&dval); |
138 | 2 | char *err = NULL; |
139 | 2 | (void)asprintf(&err, "gate bond: '%s' must be crystal before '%s' can freeze", dep, target_name); |
140 | 2 | rt->error = err; |
141 | 2 | return; |
142 | 2 | } |
143 | 2 | value_free(&dval); |
144 | 2 | } else { |
145 | 0 | value_free(&dval); |
146 | 0 | } |
147 | 26 | } |
148 | | /* Consume the bond entry (one-shot) */ |
149 | 44 | for (size_t di = 0; di < rt->bonds[bi].dep_count; di++) { |
150 | 24 | free(rt->bonds[bi].deps[di]); |
151 | 24 | if (rt->bonds[bi].dep_strategies) free(rt->bonds[bi].dep_strategies[di]); |
152 | 24 | } |
153 | 20 | free(rt->bonds[bi].deps); |
154 | 20 | free(rt->bonds[bi].dep_strategies); |
155 | 20 | free(rt->bonds[bi].target); |
156 | 20 | rt->bonds[bi] = rt->bonds[--rt->bond_count]; |
157 | 20 | break; |
158 | 22 | } |
159 | 423 | } |
160 | | |
161 | 407 | char *rt_validate_seeds(LatRuntime *rt, const char *name, LatValue *val, bool consume) { |
162 | 409 | for (size_t si = 0; si < rt->seed_count; si++) { |
163 | 6 | if (strcmp(rt->seeds[si].var_name, name) != 0) continue; |
164 | 6 | LatValue check_val = value_deep_clone(val); |
165 | 6 | LatValue result = rt->call_closure(rt->active_vm, &rt->seeds[si].contract, &check_val, 1); |
166 | 6 | value_free(&check_val); |
167 | 6 | if (rt->error) { |
168 | 0 | char *msg = NULL; |
169 | 0 | char *inner = rt->error; |
170 | 0 | rt->error = NULL; |
171 | 0 | (void)asprintf(&msg, "seed contract failed: %s", inner); |
172 | 0 | free(inner); |
173 | 0 | value_free(&result); |
174 | 0 | return msg; |
175 | 0 | } |
176 | 6 | if (!value_is_truthy(&result)) { |
177 | 4 | value_free(&result); |
178 | 4 | if (consume) { |
179 | 2 | free(rt->seeds[si].var_name); |
180 | 2 | value_free(&rt->seeds[si].contract); |
181 | 2 | rt->seeds[si] = rt->seeds[--rt->seed_count]; |
182 | 2 | } |
183 | 4 | return strdup("grow() seed contract returned false"); |
184 | 4 | } |
185 | 2 | value_free(&result); |
186 | 2 | if (consume) { |
187 | 2 | free(rt->seeds[si].var_name); |
188 | 2 | value_free(&rt->seeds[si].contract); |
189 | 2 | rt->seeds[si] = rt->seeds[--rt->seed_count]; |
190 | 2 | si--; |
191 | 2 | } |
192 | 2 | } |
193 | 403 | return NULL; |
194 | 407 | } |
195 | | |
196 | | /* ── Native builtins ── */ |
197 | | |
198 | | /* ── Native builtins ── */ |
199 | | |
200 | 354 | static LatValue native_to_string(LatValue *args, int arg_count) { |
201 | 354 | if (arg_count != 1) return value_string("to_string() expects 1 argument"); |
202 | 354 | char *s = builtin_to_string(&args[0]); |
203 | 354 | LatValue r = value_string(s); |
204 | 354 | free(s); |
205 | 354 | return r; |
206 | 354 | } |
207 | | |
208 | 144 | static LatValue native_typeof(LatValue *args, int arg_count) { |
209 | 144 | if (arg_count != 1) return value_string("typeof() expects 1 argument"); |
210 | 144 | return value_string(builtin_typeof_str(&args[0])); |
211 | 144 | } |
212 | | |
213 | 394 | static LatValue native_len(LatValue *args, int arg_count) { |
214 | 394 | if (arg_count != 1) return value_int(0); |
215 | 394 | LatValue *v = &args[0]; |
216 | 394 | if (v->type == VAL_REF) v = &v->as.ref.ref->value; |
217 | 394 | if (v->type == VAL_ARRAY) return value_int((int64_t)v->as.array.len); |
218 | 152 | if (v->type == VAL_STR) return value_int((int64_t)strlen(v->as.str_val)); |
219 | 4 | if (v->type == VAL_MAP) return value_int((int64_t)lat_map_len(v->as.map.map)); |
220 | 2 | if (v->type == VAL_BUFFER) return value_int((int64_t)v->as.buffer.len); |
221 | 0 | return value_int(0); |
222 | 2 | } |
223 | | |
224 | 4 | static LatValue native_parse_int(LatValue *args, int arg_count) { |
225 | 4 | if (arg_count != 1 || args[0].type != VAL_STR) return value_nil(); |
226 | 4 | bool ok; |
227 | 4 | int64_t v = builtin_parse_int(args[0].as.str_val, &ok); |
228 | 4 | return ok ? value_int(v) : value_nil(); |
229 | 4 | } |
230 | | |
231 | 2 | static LatValue native_parse_float(LatValue *args, int arg_count) { |
232 | 2 | if (arg_count != 1 || args[0].type != VAL_STR) return value_nil(); |
233 | 2 | bool ok; |
234 | 2 | double v = builtin_parse_float(args[0].as.str_val, &ok); |
235 | 2 | return ok ? value_float(v) : value_nil(); |
236 | 2 | } |
237 | | |
238 | 8 | static LatValue native_ord(LatValue *args, int arg_count) { |
239 | 8 | if (arg_count != 1 || args[0].type != VAL_STR) return value_int(-1); |
240 | 8 | return value_int(builtin_ord(args[0].as.str_val)); |
241 | 8 | } |
242 | | |
243 | 4 | static LatValue native_chr(LatValue *args, int arg_count) { |
244 | 4 | if (arg_count != 1 || args[0].type != VAL_INT) return value_string(""); |
245 | 4 | char *s = builtin_chr(args[0].as.int_val); |
246 | 4 | LatValue r = value_string(s); |
247 | 4 | free(s); |
248 | 4 | return r; |
249 | 4 | } |
250 | | |
251 | 6 | static LatValue native_abs(LatValue *args, int arg_count) { |
252 | 6 | if (arg_count != 1) return value_int(0); |
253 | 6 | if (args[0].type == VAL_INT) return value_int(args[0].as.int_val < 0 ? -args[0].as.int_val : args[0].as.int_val); |
254 | 2 | if (args[0].type == VAL_FLOAT) return value_float(args[0].as.float_val < 0 ? -args[0].as.float_val : args[0].as.float_val); |
255 | 0 | return value_int(0); |
256 | 2 | } |
257 | | |
258 | 5 | static LatValue native_floor(LatValue *args, int arg_count) { |
259 | 5 | if (arg_count != 1) return value_int(0); |
260 | 5 | if (args[0].type == VAL_FLOAT) return value_int((int64_t)args[0].as.float_val); |
261 | 0 | if (args[0].type == VAL_INT) return args[0]; |
262 | 0 | return value_int(0); |
263 | 0 | } |
264 | | |
265 | 2 | static LatValue native_ceil(LatValue *args, int arg_count) { |
266 | 2 | if (arg_count != 1) return value_int(0); |
267 | 2 | if (args[0].type == VAL_FLOAT) { |
268 | 2 | double v = args[0].as.float_val; |
269 | 2 | int64_t i = (int64_t)v; |
270 | 2 | return value_int(v > (double)i ? i + 1 : i); |
271 | 2 | } |
272 | 0 | if (args[0].type == VAL_INT) return args[0]; |
273 | 0 | return value_int(0); |
274 | 0 | } |
275 | | |
276 | 0 | static LatValue native_exit(LatValue *args, int arg_count) { |
277 | 0 | int code = 0; |
278 | 0 | if (arg_count >= 1 && args[0].type == VAL_INT) code = (int)args[0].as.int_val; |
279 | 0 | exit(code); |
280 | 0 | return value_unit(); |
281 | 0 | } |
282 | | |
283 | 2 | static LatValue native_error(LatValue *args, int arg_count) { |
284 | 2 | if (arg_count < 1) return value_nil(); |
285 | | /* Create an error map: {"tag" => "err", "value" => arg} */ |
286 | 2 | LatValue map_val = value_map_new(); |
287 | 2 | LatValue tag = value_string("err"); |
288 | 2 | lat_map_set(map_val.as.map.map, "tag", &tag); |
289 | 2 | LatValue val = value_deep_clone(&args[0]); |
290 | 2 | lat_map_set(map_val.as.map.map, "value", &val); |
291 | 2 | return map_val; |
292 | 2 | } |
293 | | |
294 | 6 | static LatValue native_is_error(LatValue *args, int arg_count) { |
295 | 6 | if (arg_count < 1) return value_bool(false); |
296 | 6 | if (args[0].type != VAL_MAP) return value_bool(false); |
297 | 2 | LatValue *tag = lat_map_get(args[0].as.map.map, "tag"); |
298 | 2 | if (!tag || tag->type != VAL_STR) return value_bool(false); |
299 | 2 | return value_bool(strcmp(tag->as.str_val, "err") == 0); |
300 | 2 | } |
301 | | |
302 | 822 | static LatValue native_map_new(LatValue *args, int arg_count) { |
303 | 822 | (void)args; (void)arg_count; |
304 | 822 | return value_map_new(); |
305 | 822 | } |
306 | | |
307 | 8 | static LatValue native_set_new(LatValue *args, int arg_count) { |
308 | 8 | (void)args; (void)arg_count; |
309 | 8 | return value_set_new(); |
310 | 8 | } |
311 | | |
312 | 24 | static LatValue native_set_from(LatValue *args, int arg_count) { |
313 | 24 | if (arg_count != 1 || args[0].type != VAL_ARRAY) return value_set_new(); |
314 | 24 | LatValue set = value_set_new(); |
315 | 84 | for (size_t i = 0; i < args[0].as.array.len; i++) { |
316 | 60 | char *key = value_display(&args[0].as.array.elems[i]); |
317 | 60 | LatValue clone = value_deep_clone(&args[0].as.array.elems[i]); |
318 | 60 | lat_map_set(set.as.set.map, key, &clone); |
319 | 60 | free(key); |
320 | 60 | } |
321 | 24 | return set; |
322 | 24 | } |
323 | | |
324 | 28 | static LatValue native_channel_new(LatValue *args, int arg_count) { |
325 | 28 | (void)args; (void)arg_count; |
326 | 28 | LatChannel *ch = channel_new(); |
327 | 28 | LatValue val = value_channel(ch); |
328 | 28 | channel_release(ch); |
329 | 28 | return val; |
330 | 28 | } |
331 | | |
332 | | /* ── Buffer constructors ── */ |
333 | | |
334 | 16 | static LatValue native_buffer_new(LatValue *args, int arg_count) { |
335 | 16 | if (arg_count != 1 || args[0].type != VAL_INT) return value_buffer_alloc(0); |
336 | 16 | int64_t size = args[0].as.int_val; |
337 | 16 | if (size < 0) size = 0; |
338 | 16 | return value_buffer_alloc((size_t)size); |
339 | 16 | } |
340 | | |
341 | 16 | static LatValue native_buffer_from(LatValue *args, int arg_count) { |
342 | 16 | if (arg_count != 1 || args[0].type != VAL_ARRAY) return value_buffer(NULL, 0); |
343 | 16 | size_t len = args[0].as.array.len; |
344 | 16 | uint8_t *data = malloc(len > 0 ? len : 1); |
345 | 68 | for (size_t i = 0; i < len; i++) { |
346 | 52 | if (args[0].as.array.elems[i].type == VAL_INT) |
347 | 52 | data[i] = (uint8_t)(args[0].as.array.elems[i].as.int_val & 0xFF); |
348 | 0 | else |
349 | 0 | data[i] = 0; |
350 | 52 | } |
351 | 16 | LatValue buf = value_buffer(data, len); |
352 | 16 | free(data); |
353 | 16 | return buf; |
354 | 16 | } |
355 | | |
356 | 4 | static LatValue native_buffer_from_string(LatValue *args, int arg_count) { |
357 | 4 | if (arg_count != 1 || args[0].type != VAL_STR) return value_buffer(NULL, 0); |
358 | 4 | const char *s = args[0].as.str_val; |
359 | 4 | size_t len = strlen(s); |
360 | 4 | return value_buffer((const uint8_t *)s, len); |
361 | 4 | } |
362 | | |
363 | 30 | static LatValue native_ref_new(LatValue *args, int arg_count) { |
364 | 30 | if (arg_count != 1) return value_nil(); |
365 | 30 | return value_ref(args[0]); |
366 | 30 | } |
367 | | |
368 | 0 | static LatValue native_read_file_bytes(LatValue *args, int ac) { |
369 | 0 | if (ac != 1 || args[0].type != VAL_STR) return value_nil(); |
370 | 0 | FILE *f = fopen(args[0].as.str_val, "rb"); |
371 | 0 | if (!f) return value_nil(); |
372 | 0 | fseek(f, 0, SEEK_END); |
373 | 0 | long flen = ftell(f); |
374 | 0 | fseek(f, 0, SEEK_SET); |
375 | 0 | if (flen < 0) { fclose(f); return value_nil(); } |
376 | 0 | uint8_t *data = malloc((size_t)flen); |
377 | 0 | size_t nread = fread(data, 1, (size_t)flen, f); |
378 | 0 | fclose(f); |
379 | 0 | LatValue buf = value_buffer(data, nread); |
380 | 0 | free(data); |
381 | 0 | return buf; |
382 | 0 | } |
383 | | |
384 | 0 | static LatValue native_write_file_bytes(LatValue *args, int ac) { |
385 | 0 | if (ac != 2 || args[0].type != VAL_STR || args[1].type != VAL_BUFFER) return value_bool(false); |
386 | 0 | FILE *f = fopen(args[0].as.str_val, "wb"); |
387 | 0 | if (!f) return value_bool(false); |
388 | 0 | size_t written = fwrite(args[1].as.buffer.data, 1, args[1].as.buffer.len, f); |
389 | 0 | fclose(f); |
390 | 0 | return value_bool(written == args[1].as.buffer.len); |
391 | 0 | } |
392 | | |
393 | | /* ── Phase 6: Phase system native functions ── */ |
394 | | |
395 | 28 | static LatValue native_track(LatValue *args, int ac) { |
396 | 28 | if (ac != 1 || args[0].type != VAL_STR || !current_rt) return value_unit(); |
397 | 28 | const char *name = args[0].as.str_val; |
398 | | /* Check if already tracked */ |
399 | 28 | for (size_t i = 0; i < current_rt->tracked_count; i++) { |
400 | 0 | if (strcmp(current_rt->tracked_vars[i].name, name) == 0) return value_unit(); |
401 | 0 | } |
402 | | /* Find the variable's current value (try env first, then locals) */ |
403 | 28 | LatValue val; |
404 | 28 | bool found = env_get(current_rt->env, name, &val); |
405 | 28 | if (!found) found = current_rt->find_local_value(current_rt->active_vm, name, &val); |
406 | 28 | if (!found) { |
407 | 2 | char *msg = NULL; |
408 | 2 | (void)asprintf(&msg, "track: undefined variable '%s'", name); |
409 | 2 | current_rt->error = msg; |
410 | 2 | return value_unit(); |
411 | 2 | } |
412 | | /* Register tracking */ |
413 | 26 | if (current_rt->tracked_count >= current_rt->tracked_cap) { |
414 | 26 | current_rt->tracked_cap = current_rt->tracked_cap ? current_rt->tracked_cap * 2 : 4; |
415 | 26 | current_rt->tracked_vars = realloc(current_rt->tracked_vars, |
416 | 26 | current_rt->tracked_cap * sizeof(*current_rt->tracked_vars)); |
417 | 26 | } |
418 | 26 | size_t idx = current_rt->tracked_count++; |
419 | 26 | current_rt->tracking_active = true; |
420 | 26 | current_rt->tracked_vars[idx].name = strdup(name); |
421 | 26 | current_rt->tracked_vars[idx].snapshots = NULL; |
422 | 26 | current_rt->tracked_vars[idx].snap_count = 0; |
423 | 26 | current_rt->tracked_vars[idx].snap_cap = 0; |
424 | | /* Record initial snapshot */ |
425 | 26 | rt_record_history(current_rt, name, &val); |
426 | 26 | value_free(&val); |
427 | 26 | return value_unit(); |
428 | 28 | } |
429 | | |
430 | 16 | static LatValue native_phases(LatValue *args, int ac) { |
431 | 16 | if (ac != 1 || args[0].type != VAL_STR || !current_rt) { |
432 | 0 | return value_array(NULL, 0); |
433 | 0 | } |
434 | 16 | const char *name = args[0].as.str_val; |
435 | 16 | for (size_t i = 0; i < current_rt->tracked_count; i++) { |
436 | 14 | if (strcmp(current_rt->tracked_vars[i].name, name) != 0) continue; |
437 | 14 | size_t n = current_rt->tracked_vars[i].snap_count; |
438 | 14 | LatValue *elems = malloc(n * sizeof(LatValue)); |
439 | 46 | for (size_t j = 0; j < n; j++) { |
440 | 32 | LatValue m = value_map_new(); |
441 | 32 | LatValue phase_val = value_string(current_rt->tracked_vars[i].snapshots[j].phase); |
442 | 32 | LatValue val_clone = value_deep_clone(¤t_rt->tracked_vars[i].snapshots[j].value); |
443 | 32 | LatValue line_val = value_int(current_rt->tracked_vars[i].snapshots[j].line); |
444 | 32 | LatValue fn_val = current_rt->tracked_vars[i].snapshots[j].fn_name |
445 | 32 | ? value_string(current_rt->tracked_vars[i].snapshots[j].fn_name) |
446 | 32 | : value_nil(); |
447 | 32 | lat_map_set(m.as.map.map, "phase", &phase_val); |
448 | 32 | lat_map_set(m.as.map.map, "value", &val_clone); |
449 | 32 | lat_map_set(m.as.map.map, "line", &line_val); |
450 | 32 | lat_map_set(m.as.map.map, "fn", &fn_val); |
451 | 32 | elems[j] = m; |
452 | 32 | } |
453 | 14 | LatValue arr = value_array(elems, n); |
454 | 14 | free(elems); |
455 | 14 | return arr; |
456 | 14 | } |
457 | 2 | return value_array(NULL, 0); |
458 | 16 | } |
459 | | |
460 | | /// @builtin history(name: String) -> Array |
461 | | /// @category Temporal |
462 | | /// Returns the full enriched timeline of a tracked variable as an array of Maps |
463 | | /// with keys: phase, value, line, fn. |
464 | | /// @example history(x) // [{phase: "fluid", value: 10, line: 3, fn: "main"}, ...] |
465 | 8 | static LatValue native_history(LatValue *args, int ac) { |
466 | 8 | if (ac != 1 || args[0].type != VAL_STR || !current_rt) |
467 | 0 | return value_array(NULL, 0); |
468 | 8 | const char *name = args[0].as.str_val; |
469 | 8 | for (size_t i = 0; i < current_rt->tracked_count; i++) { |
470 | 6 | if (strcmp(current_rt->tracked_vars[i].name, name) != 0) continue; |
471 | 6 | size_t n = current_rt->tracked_vars[i].snap_count; |
472 | 6 | LatValue *elems = malloc(n * sizeof(LatValue)); |
473 | 20 | for (size_t j = 0; j < n; j++) { |
474 | 14 | LatValue m = value_map_new(); |
475 | 14 | LatValue phase_val = value_string(current_rt->tracked_vars[i].snapshots[j].phase); |
476 | 14 | LatValue val_clone = value_deep_clone(¤t_rt->tracked_vars[i].snapshots[j].value); |
477 | 14 | LatValue line_val = value_int(current_rt->tracked_vars[i].snapshots[j].line); |
478 | 14 | LatValue fn_val = current_rt->tracked_vars[i].snapshots[j].fn_name |
479 | 14 | ? value_string(current_rt->tracked_vars[i].snapshots[j].fn_name) |
480 | 14 | : value_nil(); |
481 | 14 | lat_map_set(m.as.map.map, "phase", &phase_val); |
482 | 14 | lat_map_set(m.as.map.map, "value", &val_clone); |
483 | 14 | lat_map_set(m.as.map.map, "line", &line_val); |
484 | 14 | lat_map_set(m.as.map.map, "fn", &fn_val); |
485 | 14 | elems[j] = m; |
486 | 14 | } |
487 | 6 | LatValue arr = value_array(elems, n); |
488 | 6 | free(elems); |
489 | 6 | return arr; |
490 | 6 | } |
491 | 2 | return value_array(NULL, 0); |
492 | 8 | } |
493 | | |
494 | 14 | static LatValue native_rewind(LatValue *args, int ac) { |
495 | 14 | if (ac != 2 || args[0].type != VAL_STR || args[1].type != VAL_INT || !current_rt) |
496 | 0 | return value_nil(); |
497 | 14 | const char *name = args[0].as.str_val; |
498 | 14 | int64_t steps = args[1].as.int_val; |
499 | 14 | for (size_t i = 0; i < current_rt->tracked_count; i++) { |
500 | 14 | if (strcmp(current_rt->tracked_vars[i].name, name) != 0) continue; |
501 | 14 | int64_t idx = (int64_t)current_rt->tracked_vars[i].snap_count - 1 - steps; |
502 | 14 | if (idx < 0 || idx >= (int64_t)current_rt->tracked_vars[i].snap_count) |
503 | 2 | return value_nil(); |
504 | 12 | return value_deep_clone(¤t_rt->tracked_vars[i].snapshots[idx].value); |
505 | 14 | } |
506 | 0 | return value_nil(); |
507 | 14 | } |
508 | | |
509 | 12 | static LatValue native_pressurize(LatValue *args, int ac) { |
510 | 12 | if (ac != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR || !current_rt) |
511 | 0 | return value_unit(); |
512 | 12 | const char *name = args[0].as.str_val; |
513 | 12 | const char *mode = args[1].as.str_val; |
514 | | /* Validate mode */ |
515 | 12 | if (strcmp(mode, "no_grow") != 0 && strcmp(mode, "no_shrink") != 0 && |
516 | 12 | strcmp(mode, "no_resize") != 0 && strcmp(mode, "read_heavy") != 0) |
517 | 0 | return value_unit(); |
518 | | /* Update existing or add new */ |
519 | 12 | for (size_t i = 0; i < current_rt->pressure_count; i++) { |
520 | 0 | if (strcmp(current_rt->pressures[i].name, name) == 0) { |
521 | 0 | free(current_rt->pressures[i].mode); |
522 | 0 | current_rt->pressures[i].mode = strdup(mode); |
523 | 0 | return value_unit(); |
524 | 0 | } |
525 | 0 | } |
526 | 12 | if (current_rt->pressure_count >= current_rt->pressure_cap) { |
527 | 12 | current_rt->pressure_cap = current_rt->pressure_cap ? current_rt->pressure_cap * 2 : 4; |
528 | 12 | current_rt->pressures = realloc(current_rt->pressures, |
529 | 12 | current_rt->pressure_cap * sizeof(*current_rt->pressures)); |
530 | 12 | } |
531 | 12 | size_t idx = current_rt->pressure_count++; |
532 | 12 | current_rt->pressures[idx].name = strdup(name); |
533 | 12 | current_rt->pressures[idx].mode = strdup(mode); |
534 | 12 | return value_unit(); |
535 | 12 | } |
536 | | |
537 | 2 | static LatValue native_depressurize(LatValue *args, int ac) { |
538 | 2 | if (ac != 1 || args[0].type != VAL_STR || !current_rt) return value_unit(); |
539 | 2 | const char *name = args[0].as.str_val; |
540 | 2 | for (size_t i = 0; i < current_rt->pressure_count; i++) { |
541 | 2 | if (strcmp(current_rt->pressures[i].name, name) == 0) { |
542 | 2 | free(current_rt->pressures[i].name); |
543 | 2 | free(current_rt->pressures[i].mode); |
544 | 2 | current_rt->pressures[i] = current_rt->pressures[--current_rt->pressure_count]; |
545 | 2 | return value_unit(); |
546 | 2 | } |
547 | 2 | } |
548 | 0 | return value_unit(); |
549 | 2 | } |
550 | | |
551 | 4 | static LatValue native_pressure_of(LatValue *args, int ac) { |
552 | 4 | if (ac != 1 || args[0].type != VAL_STR || !current_rt) return value_nil(); |
553 | 4 | const char *name = args[0].as.str_val; |
554 | 4 | for (size_t i = 0; i < current_rt->pressure_count; i++) { |
555 | 2 | if (strcmp(current_rt->pressures[i].name, name) == 0) |
556 | 2 | return value_string(current_rt->pressures[i].mode); |
557 | 2 | } |
558 | 2 | return value_nil(); |
559 | 4 | } |
560 | | /* Full grow() implementation as native function */ |
561 | 2 | static LatValue native_grow(LatValue *args, int ac) { |
562 | 2 | if (ac != 1 || args[0].type != VAL_STR || !current_rt) return value_nil(); |
563 | 2 | const char *vname = args[0].as.str_val; |
564 | 2 | LatValue val; |
565 | 2 | if (!current_rt->get_var_by_name(current_rt->active_vm, vname, &val)) return value_nil(); |
566 | | |
567 | | /* Validate and consume all seeds */ |
568 | 2 | char *err = rt_validate_seeds(current_rt, vname, &val, true); |
569 | 2 | if (err) { |
570 | 1 | value_free(&val); |
571 | | /* Report error through StackVM */ |
572 | 1 | current_rt->error = err; |
573 | 1 | return value_nil(); |
574 | 1 | } |
575 | | |
576 | | /* Freeze */ |
577 | 1 | LatValue frozen = value_freeze(val); |
578 | 1 | LatValue ret = value_deep_clone(&frozen); |
579 | 1 | current_rt->set_var_by_name(current_rt->active_vm, vname, value_deep_clone(&frozen)); |
580 | 1 | rt_record_history(current_rt, vname, &frozen); |
581 | 1 | value_free(&frozen); |
582 | | |
583 | | /* Cascade and reactions */ |
584 | | |
585 | 1 | rt_freeze_cascade(current_rt, vname); |
586 | 1 | rt_fire_reactions(current_rt, vname, "crystal"); |
587 | | |
588 | 1 | return ret; |
589 | 2 | } |
590 | | |
591 | 58 | static LatValue native_phase_of(LatValue *args, int arg_count) { |
592 | 58 | if (arg_count != 1) return value_string("unknown"); |
593 | 58 | return value_string(builtin_phase_of_str(&args[0])); |
594 | 58 | } |
595 | | |
596 | 184 | static LatValue native_assert(LatValue *args, int arg_count) { |
597 | 184 | if (arg_count < 1) return value_unit(); |
598 | 184 | bool ok = false; |
599 | 184 | if (args[0].type == VAL_BOOL) ok = args[0].as.bool_val; |
600 | 0 | else if (args[0].type == VAL_INT) ok = args[0].as.int_val != 0; |
601 | 0 | else ok = args[0].type != VAL_NIL; |
602 | 184 | if (!ok) { |
603 | 6 | const char *msg = (arg_count >= 2 && args[1].type == VAL_STR) ? args[1].as.str_val : "assertion failed"; |
604 | | /* Use StackVM error mechanism instead of exit() so tests can catch failures */ |
605 | 6 | if (current_rt) { |
606 | 6 | char *err = NULL; |
607 | 6 | (void)asprintf(&err, "assertion failed: %s", msg); |
608 | 6 | current_rt->error = err; |
609 | 6 | } else { |
610 | 0 | fprintf(stderr, "assertion failed: %s\n", msg); |
611 | 0 | exit(1); |
612 | 0 | } |
613 | 6 | } |
614 | 184 | return value_unit(); |
615 | 184 | } |
616 | | |
617 | 4 | static LatValue native_version(LatValue *args, int arg_count) { |
618 | 4 | (void)args; (void)arg_count; |
619 | 4 | return value_string(LATTICE_VERSION); |
620 | 4 | } |
621 | | |
622 | 0 | static LatValue native_input(LatValue *args, int arg_count) { |
623 | 0 | const char *prompt = NULL; |
624 | 0 | if (arg_count >= 1 && args[0].type == VAL_STR) prompt = args[0].as.str_val; |
625 | 0 | char *line = builtin_input(prompt); |
626 | 0 | if (!line) return value_nil(); |
627 | 0 | LatValue r = value_string(line); |
628 | 0 | free(line); |
629 | 0 | return r; |
630 | 0 | } |
631 | | |
632 | | /* ── Math natives (via math_ops.h) ── */ |
633 | | |
634 | | #define MATH1(cname, mathfn) \ |
635 | 57 | static LatValue cname(LatValue *args, int ac) { \ |
636 | 57 | if (ac != 1) return value_nil(); \ |
637 | 57 | char *err = NULL; \ |
638 | 57 | LatValue r = mathfn(&args[0], &err); \ |
639 | 57 | if (err) { current_rt->error = err; return value_nil(); } \ |
640 | 57 | return r; \ |
641 | 57 | } |
642 | | |
643 | | #define MATH2(cname, mathfn) \ |
644 | 18 | static LatValue cname(LatValue *args, int ac) { \ |
645 | 18 | if (ac != 2) return value_nil(); \ |
646 | 18 | char *err = NULL; \ |
647 | 18 | LatValue r = mathfn(&args[0], &args[1], &err); \ |
648 | 18 | if (err) { current_rt->error = err; return value_nil(); } \ |
649 | 18 | return r; \ |
650 | 18 | } |
651 | | |
652 | | #define MATH3(cname, mathfn) \ |
653 | 10 | static LatValue cname(LatValue *args, int ac) { \ |
654 | 10 | if (ac != 3) return value_nil(); \ |
655 | 10 | char *err = NULL; \ |
656 | 10 | LatValue r = mathfn(&args[0], &args[1], &args[2], &err); \ |
657 | 10 | if (err) { current_rt->error = err; return value_nil(); } \ |
658 | 10 | return r; \ |
659 | 10 | } |
660 | | |
661 | 4 | MATH1(native_round, math_round) |
662 | 9 | MATH1(native_sqrt, math_sqrt) |
663 | 4 | MATH2(native_pow, math_pow) |
664 | 4 | MATH2(native_min, math_min) |
665 | 4 | MATH2(native_max, math_max) |
666 | 2 | MATH1(native_log, math_log) |
667 | 2 | MATH1(native_log2, math_log2) |
668 | 2 | MATH1(native_log10, math_log10) |
669 | 6 | MATH1(native_sin, math_sin) |
670 | 2 | MATH1(native_cos, math_cos) |
671 | 2 | MATH1(native_tan, math_tan) |
672 | 2 | MATH1(native_asin, math_asin) |
673 | 2 | MATH1(native_acos, math_acos) |
674 | 2 | MATH1(native_atan, math_atan) |
675 | 2 | MATH2(native_atan2, math_atan2) |
676 | 2 | MATH1(native_exp, math_exp) |
677 | 6 | MATH1(native_sign, math_sign) |
678 | 2 | MATH2(native_gcd, math_gcd) |
679 | 2 | MATH2(native_lcm, math_lcm) |
680 | 4 | MATH1(native_is_nan, math_is_nan) |
681 | 4 | MATH1(native_is_inf, math_is_inf) |
682 | 2 | MATH1(native_sinh, math_sinh) |
683 | 2 | MATH1(native_cosh, math_cosh) |
684 | 2 | MATH1(native_tanh, math_tanh) |
685 | 4 | MATH3(native_lerp, math_lerp) |
686 | 6 | MATH3(native_clamp, math_clamp) |
687 | | |
688 | 2 | static LatValue native_random(LatValue *args, int ac) { |
689 | 2 | (void)args; (void)ac; |
690 | 2 | return math_random(); |
691 | 2 | } |
692 | 2 | static LatValue native_random_int(LatValue *args, int ac) { |
693 | 2 | if (ac != 2) return value_nil(); |
694 | 2 | char *err = NULL; |
695 | 2 | LatValue r = math_random_int(&args[0], &args[1], &err); |
696 | 2 | if (err) { current_rt->error = err; return value_nil(); } |
697 | 2 | return r; |
698 | 2 | } |
699 | 5 | static LatValue native_math_pi(LatValue *args, int ac) { |
700 | 5 | (void)args; (void)ac; |
701 | 5 | return math_pi(); |
702 | 5 | } |
703 | 4 | static LatValue native_math_e(LatValue *args, int ac) { |
704 | 4 | (void)args; (void)ac; |
705 | 4 | return math_e(); |
706 | 4 | } |
707 | | |
708 | | #undef MATH1 |
709 | | #undef MATH2 |
710 | | #undef MATH3 |
711 | | |
712 | | /* ── File system natives ── */ |
713 | | |
714 | 8 | static LatValue native_read_file(LatValue *args, int ac) { |
715 | 8 | if (ac != 1 || args[0].type != VAL_STR) { current_rt->error = strdup("read_file() expects (path: String)"); return value_nil(); } |
716 | 8 | char *contents = builtin_read_file(args[0].as.str_val); |
717 | 8 | if (!contents) return value_nil(); |
718 | 8 | return value_string_owned(contents); |
719 | 8 | } |
720 | 26 | static LatValue native_write_file(LatValue *args, int ac) { |
721 | 26 | if (ac != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { current_rt->error = strdup("write_file() expects (path: String, data: String)"); return value_bool(false); } |
722 | 26 | return value_bool(builtin_write_file(args[0].as.str_val, args[1].as.str_val)); |
723 | 26 | } |
724 | 16 | static LatValue native_file_exists(LatValue *args, int ac) { |
725 | 16 | if (ac != 1 || args[0].type != VAL_STR) { current_rt->error = strdup("file_exists() expects (path: String)"); return value_bool(false); } |
726 | 14 | return value_bool(fs_file_exists(args[0].as.str_val)); |
727 | 16 | } |
728 | 26 | static LatValue native_delete_file(LatValue *args, int ac) { |
729 | 26 | if (ac != 1 || args[0].type != VAL_STR) { |
730 | 2 | current_rt->error = strdup("delete_file: expected (path: Str)"); |
731 | 2 | return value_bool(false); |
732 | 2 | } |
733 | 24 | char *err = NULL; |
734 | 24 | bool ok = fs_delete_file(args[0].as.str_val, &err); |
735 | 24 | if (err) { current_rt->error = err; return value_bool(false); } |
736 | 22 | return value_bool(ok); |
737 | 24 | } |
738 | 6 | static LatValue native_list_dir(LatValue *args, int ac) { |
739 | 6 | if (ac != 1 || args[0].type != VAL_STR) { |
740 | 2 | current_rt->error = strdup("list_dir: expected (path: Str)"); |
741 | 2 | return value_array(NULL, 0); |
742 | 2 | } |
743 | 4 | char *err = NULL; |
744 | 4 | size_t count = 0; |
745 | 4 | char **entries = fs_list_dir(args[0].as.str_val, &count, &err); |
746 | 4 | if (err) { current_rt->error = err; return value_array(NULL, 0); } |
747 | 2 | if (!entries) return value_array(NULL, 0); |
748 | 2 | LatValue *elems = malloc((count > 0 ? count : 1) * sizeof(LatValue)); |
749 | 3.25k | for (size_t i = 0; i < count; i++) { |
750 | 3.24k | elems[i] = value_string(entries[i]); |
751 | 3.24k | free(entries[i]); |
752 | 3.24k | } |
753 | 2 | free(entries); |
754 | 2 | LatValue r = value_array(elems, count); |
755 | 2 | free(elems); |
756 | 2 | return r; |
757 | 2 | } |
758 | 6 | static LatValue native_append_file(LatValue *args, int ac) { |
759 | 6 | if (ac != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { current_rt->error = strdup("append_file() expects (path: String, data: String)"); return value_bool(false); } |
760 | 4 | char *err = NULL; |
761 | 4 | bool ok = fs_append_file(args[0].as.str_val, args[1].as.str_val, &err); |
762 | 4 | if (err) { current_rt->error = err; return value_bool(false); } |
763 | 4 | return value_bool(ok); |
764 | 4 | } |
765 | 6 | static LatValue native_mkdir(LatValue *args, int ac) { |
766 | 6 | if (ac != 1 || args[0].type != VAL_STR) return value_bool(false); |
767 | 6 | char *err = NULL; |
768 | 6 | bool ok = fs_mkdir(args[0].as.str_val, &err); |
769 | 6 | if (err) { current_rt->error = err; return value_bool(false); } |
770 | 6 | return value_bool(ok); |
771 | 6 | } |
772 | 2 | static LatValue native_fs_rename(LatValue *args, int ac) { |
773 | 2 | if (ac != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { |
774 | 0 | current_rt->error = strdup("rename: expected (from: Str, to: Str)"); |
775 | 0 | return value_bool(false); |
776 | 0 | } |
777 | 2 | char *err = NULL; |
778 | 2 | bool ok = fs_rename(args[0].as.str_val, args[1].as.str_val, &err); |
779 | 2 | if (err) { current_rt->error = err; return value_bool(false); } |
780 | 2 | return value_bool(ok); |
781 | 2 | } |
782 | 10 | static LatValue native_is_dir(LatValue *args, int ac) { |
783 | 10 | if (ac != 1 || args[0].type != VAL_STR) return value_bool(false); |
784 | 10 | return value_bool(fs_is_dir(args[0].as.str_val)); |
785 | 10 | } |
786 | 6 | static LatValue native_is_file(LatValue *args, int ac) { |
787 | 6 | if (ac != 1 || args[0].type != VAL_STR) return value_bool(false); |
788 | 6 | return value_bool(fs_is_file(args[0].as.str_val)); |
789 | 6 | } |
790 | 8 | static LatValue native_rmdir(LatValue *args, int ac) { |
791 | 8 | if (ac != 1 || args[0].type != VAL_STR) { |
792 | 0 | current_rt->error = strdup("rmdir: expected (path: Str)"); |
793 | 0 | return value_bool(false); |
794 | 0 | } |
795 | 8 | char *err = NULL; |
796 | 8 | bool ok = fs_rmdir(args[0].as.str_val, &err); |
797 | 8 | if (err) { current_rt->error = err; return value_bool(false); } |
798 | 6 | return value_bool(ok); |
799 | 8 | } |
800 | 4 | static LatValue native_glob(LatValue *args, int ac) { |
801 | 4 | if (ac != 1 || args[0].type != VAL_STR) return value_array(NULL, 0); |
802 | 4 | char *err = NULL; |
803 | 4 | size_t count = 0; |
804 | 4 | char **matches = fs_glob(args[0].as.str_val, &count, &err); |
805 | 4 | if (err) { current_rt->error = err; return value_array(NULL, 0); } |
806 | 4 | if (!matches) return value_array(NULL, 0); |
807 | 2 | LatValue *elems = malloc((count > 0 ? count : 1) * sizeof(LatValue)); |
808 | 6 | for (size_t i = 0; i < count; i++) { |
809 | 4 | elems[i] = value_string(matches[i]); |
810 | 4 | free(matches[i]); |
811 | 4 | } |
812 | 2 | free(matches); |
813 | 2 | LatValue r = value_array(elems, count); |
814 | 2 | free(elems); |
815 | 2 | return r; |
816 | 4 | } |
817 | 6 | static LatValue native_stat(LatValue *args, int ac) { |
818 | 6 | if (ac != 1 || args[0].type != VAL_STR) { |
819 | 0 | current_rt->error = strdup("stat: expected (path: Str)"); |
820 | 0 | return value_nil(); |
821 | 0 | } |
822 | 6 | int64_t sz, mt, md; |
823 | 6 | const char *tp; |
824 | 6 | char *err = NULL; |
825 | 6 | if (!fs_stat(args[0].as.str_val, &sz, &mt, &md, &tp, &err)) { |
826 | 2 | if (err) current_rt->error = err; |
827 | 2 | return value_nil(); |
828 | 2 | } |
829 | 4 | LatValue map = value_map_new(); |
830 | 4 | LatValue v_sz = value_int(sz); lat_map_set(map.as.map.map, "size", &v_sz); |
831 | 4 | LatValue v_mt = value_int(mt); lat_map_set(map.as.map.map, "mtime", &v_mt); |
832 | 4 | LatValue v_md = value_int(md); lat_map_set(map.as.map.map, "mode", &v_md); |
833 | 4 | LatValue v_tp = value_string(tp); lat_map_set(map.as.map.map, "type", &v_tp); |
834 | 4 | LatValue v_pm = value_int(md); lat_map_set(map.as.map.map, "permissions", &v_pm); |
835 | 4 | return map; |
836 | 6 | } |
837 | 4 | static LatValue native_copy_file(LatValue *args, int ac) { |
838 | 4 | if (ac != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { |
839 | 0 | current_rt->error = strdup("copy_file: expected (src: Str, dst: Str)"); |
840 | 0 | return value_bool(false); |
841 | 0 | } |
842 | 4 | char *err = NULL; |
843 | 4 | bool ok = fs_copy_file(args[0].as.str_val, args[1].as.str_val, &err); |
844 | 4 | if (err) { current_rt->error = err; return value_bool(false); } |
845 | 2 | return value_bool(ok); |
846 | 4 | } |
847 | 4 | static LatValue native_realpath(LatValue *args, int ac) { |
848 | 4 | if (ac != 1 || args[0].type != VAL_STR) { |
849 | 0 | current_rt->error = strdup("realpath: expected (path: Str)"); |
850 | 0 | return value_nil(); |
851 | 0 | } |
852 | 4 | char *err = NULL; |
853 | 4 | char *r = fs_realpath(args[0].as.str_val, &err); |
854 | 4 | if (err) { current_rt->error = err; return value_nil(); } |
855 | 2 | if (!r) return value_nil(); |
856 | 2 | return value_string_owned(r); |
857 | 2 | } |
858 | 2 | static LatValue native_tempdir(LatValue *args, int ac) { |
859 | 2 | (void)args; (void)ac; |
860 | 2 | char *err = NULL; char *r = fs_tempdir(&err); |
861 | 2 | if (err) { current_rt->error = err; return value_nil(); } |
862 | 2 | if (!r) return value_nil(); |
863 | 2 | return value_string_owned(r); |
864 | 2 | } |
865 | 4 | static LatValue native_tempfile(LatValue *args, int ac) { |
866 | 4 | (void)args; (void)ac; |
867 | 4 | char *err = NULL; char *r = fs_tempfile(&err); |
868 | 4 | if (err) { current_rt->error = err; return value_nil(); } |
869 | 4 | if (!r) return value_nil(); |
870 | 4 | return value_string_owned(r); |
871 | 4 | } |
872 | 2 | static LatValue native_chmod(LatValue *args, int ac) { |
873 | 2 | if (ac != 2 || args[0].type != VAL_STR || args[1].type != VAL_INT) return value_bool(false); |
874 | 2 | char *err = NULL; |
875 | 2 | bool ok = fs_chmod(args[0].as.str_val, (int)args[1].as.int_val, &err); |
876 | 2 | if (err) { current_rt->error = err; return value_bool(false); } |
877 | 2 | return value_bool(ok); |
878 | 2 | } |
879 | 4 | static LatValue native_file_size(LatValue *args, int ac) { |
880 | 4 | if (ac != 1 || args[0].type != VAL_STR) { |
881 | 0 | current_rt->error = strdup("file_size: expected (path: Str)"); |
882 | 0 | return value_int(-1); |
883 | 0 | } |
884 | 4 | char *err = NULL; |
885 | 4 | int64_t sz = fs_file_size(args[0].as.str_val, &err); |
886 | 4 | if (err) { current_rt->error = err; return value_int(-1); } |
887 | 2 | return value_int(sz); |
888 | 4 | } |
889 | | |
890 | | /* ── Path natives ── */ |
891 | | |
892 | 13 | static LatValue native_path_join(LatValue *args, int ac) { |
893 | 13 | if (ac < 1) { current_rt->error = strdup("path_join() expects at least 1 argument"); return value_string(""); } |
894 | 37 | for (int i = 0; i < ac; i++) { |
895 | 26 | if (args[i].type != VAL_STR) { current_rt->error = strdup("path_join() expects (String...)"); return value_string(""); } |
896 | 26 | } |
897 | 11 | const char **parts = malloc((size_t)ac * sizeof(char *)); |
898 | 35 | for (int i = 0; i < ac; i++) |
899 | 24 | parts[i] = args[i].as.str_val; |
900 | 11 | char *r = path_join(parts, (size_t)ac); |
901 | 11 | free(parts); return value_string_owned(r); |
902 | 13 | } |
903 | 10 | static LatValue native_path_dir(LatValue *args, int ac) { |
904 | 10 | if (ac != 1 || args[0].type != VAL_STR) { current_rt->error = strdup("path_dir() expects (path: String)"); return value_string("."); } |
905 | 8 | return value_string_owned(path_dir(args[0].as.str_val)); |
906 | 10 | } |
907 | 8 | static LatValue native_path_base(LatValue *args, int ac) { |
908 | 8 | if (ac != 1 || args[0].type != VAL_STR) { current_rt->error = strdup("path_base() expects (path: String)"); return value_string(""); } |
909 | 6 | return value_string_owned(path_base(args[0].as.str_val)); |
910 | 8 | } |
911 | 12 | static LatValue native_path_ext(LatValue *args, int ac) { |
912 | 12 | if (ac != 1 || args[0].type != VAL_STR) { current_rt->error = strdup("path_ext() expects (path: String)"); return value_string(""); } |
913 | 10 | return value_string_owned(path_ext(args[0].as.str_val)); |
914 | 12 | } |
915 | | |
916 | | /* ── Network TCP natives ── */ |
917 | | |
918 | 4 | static LatValue native_tcp_listen(LatValue *args, int ac) { |
919 | 4 | if (ac != 2 || args[0].type != VAL_STR || args[1].type != VAL_INT) { |
920 | 2 | current_rt->error = strdup("tcp_listen: expected (host: Str, port: Int)"); |
921 | 2 | return value_int(-1); |
922 | 2 | } |
923 | 2 | char *err = NULL; |
924 | 2 | int fd = net_tcp_listen(args[0].as.str_val, (int)args[1].as.int_val, &err); |
925 | 2 | if (err) { current_rt->error = err; return value_int(-1); } |
926 | 2 | return value_int(fd); |
927 | 2 | } |
928 | 0 | static LatValue native_tcp_accept(LatValue *args, int ac) { |
929 | 0 | if (ac != 1 || args[0].type != VAL_INT) { |
930 | 0 | current_rt->error = strdup("tcp_accept: expected (fd: Int)"); |
931 | 0 | return value_int(-1); |
932 | 0 | } |
933 | 0 | char *err = NULL; |
934 | 0 | int fd = net_tcp_accept((int)args[0].as.int_val, &err); |
935 | 0 | if (err) { current_rt->error = err; return value_int(-1); } |
936 | 0 | return value_int(fd); |
937 | 0 | } |
938 | 0 | static LatValue native_tcp_connect(LatValue *args, int ac) { |
939 | 0 | if (ac != 2 || args[0].type != VAL_STR || args[1].type != VAL_INT) { |
940 | 0 | current_rt->error = strdup("tcp_connect: expected (host: Str, port: Int)"); |
941 | 0 | return value_int(-1); |
942 | 0 | } |
943 | 0 | char *err = NULL; |
944 | 0 | int fd = net_tcp_connect(args[0].as.str_val, (int)args[1].as.int_val, &err); |
945 | 0 | if (err) { current_rt->error = err; return value_int(-1); } |
946 | 0 | return value_int(fd); |
947 | 0 | } |
948 | 2 | static LatValue native_tcp_read(LatValue *args, int ac) { |
949 | 2 | if (ac != 1 || args[0].type != VAL_INT) { |
950 | 2 | current_rt->error = strdup("tcp_read: expected (fd: Int)"); |
951 | 2 | return value_string(""); |
952 | 2 | } |
953 | 0 | char *err = NULL; |
954 | 0 | char *data = net_tcp_read((int)args[0].as.int_val, &err); |
955 | 0 | if (err) { current_rt->error = err; return value_string(""); } |
956 | 0 | if (!data) return value_string(""); |
957 | 0 | return value_string_owned(data); |
958 | 0 | } |
959 | 0 | static LatValue native_tcp_read_bytes(LatValue *args, int ac) { |
960 | 0 | if (ac != 2 || args[0].type != VAL_INT || args[1].type != VAL_INT) return value_string(""); |
961 | 0 | char *err = NULL; |
962 | 0 | char *data = net_tcp_read_bytes((int)args[0].as.int_val, (size_t)args[1].as.int_val, &err); |
963 | 0 | if (err) { current_rt->error = err; return value_string(""); } |
964 | 0 | if (!data) return value_string(""); |
965 | 0 | return value_string_owned(data); |
966 | 0 | } |
967 | 0 | static LatValue native_tcp_write(LatValue *args, int ac) { |
968 | 0 | if (ac != 2 || args[0].type != VAL_INT || args[1].type != VAL_STR) return value_bool(false); |
969 | 0 | char *err = NULL; |
970 | 0 | bool ok = net_tcp_write((int)args[0].as.int_val, args[1].as.str_val, strlen(args[1].as.str_val), &err); |
971 | 0 | if (err) { current_rt->error = err; return value_bool(false); } |
972 | 0 | return value_bool(ok); |
973 | 0 | } |
974 | 2 | static LatValue native_tcp_close(LatValue *args, int ac) { |
975 | 2 | if (ac != 1 || args[0].type != VAL_INT) return value_unit(); |
976 | 2 | net_tcp_close((int)args[0].as.int_val); return value_unit(); |
977 | 2 | } |
978 | 0 | static LatValue native_tcp_peer_addr(LatValue *args, int ac) { |
979 | 0 | if (ac != 1 || args[0].type != VAL_INT) return value_nil(); |
980 | 0 | char *err = NULL; |
981 | 0 | char *addr = net_tcp_peer_addr((int)args[0].as.int_val, &err); |
982 | 0 | if (err) { current_rt->error = err; return value_nil(); } |
983 | 0 | if (!addr) return value_nil(); |
984 | 0 | return value_string_owned(addr); |
985 | 0 | } |
986 | 0 | static LatValue native_tcp_set_timeout(LatValue *args, int ac) { |
987 | 0 | if (ac != 2 || args[0].type != VAL_INT || args[1].type != VAL_INT) return value_bool(false); |
988 | 0 | char *err = NULL; |
989 | 0 | bool ok = net_tcp_set_timeout((int)args[0].as.int_val, (int)args[1].as.int_val, &err); |
990 | 0 | if (err) { current_rt->error = err; return value_bool(false); } |
991 | 0 | return value_bool(ok); |
992 | 0 | } |
993 | | |
994 | | /* ── TLS natives ── */ |
995 | | |
996 | 2 | static LatValue native_tls_connect(LatValue *args, int ac) { |
997 | 2 | if (ac != 2 || args[0].type != VAL_STR || args[1].type != VAL_INT) { |
998 | 2 | current_rt->error = strdup("tls_connect: expected (host: Str, port: Int)"); |
999 | 2 | return value_int(-1); |
1000 | 2 | } |
1001 | 0 | char *err = NULL; |
1002 | 0 | int fd = net_tls_connect(args[0].as.str_val, (int)args[1].as.int_val, &err); |
1003 | 0 | if (err) { current_rt->error = err; return value_int(-1); } |
1004 | 0 | return value_int(fd); |
1005 | 0 | } |
1006 | 2 | static LatValue native_tls_read(LatValue *args, int ac) { |
1007 | 2 | if (ac != 1 || args[0].type != VAL_INT) { current_rt->error = strdup("tls_read() expects (fd: Int)"); return value_string(""); } |
1008 | 0 | char *err = NULL; |
1009 | 0 | char *data = net_tls_read((int)args[0].as.int_val, &err); |
1010 | 0 | if (err) { current_rt->error = err; return value_string(""); } |
1011 | 0 | if (!data) return value_string(""); |
1012 | 0 | return value_string_owned(data); |
1013 | 0 | } |
1014 | 0 | static LatValue native_tls_read_bytes(LatValue *args, int ac) { |
1015 | 0 | if (ac != 2 || args[0].type != VAL_INT || args[1].type != VAL_INT) { current_rt->error = strdup("tls_read_bytes() expects (fd: Int, n: Int)"); return value_string(""); } |
1016 | 0 | char *err = NULL; |
1017 | 0 | char *data = net_tls_read_bytes((int)args[0].as.int_val, (size_t)args[1].as.int_val, &err); |
1018 | 0 | if (err) { current_rt->error = err; return value_string(""); } |
1019 | 0 | if (!data) return value_string(""); |
1020 | 0 | return value_string_owned(data); |
1021 | 0 | } |
1022 | 0 | static LatValue native_tls_write(LatValue *args, int ac) { |
1023 | 0 | if (ac != 2 || args[0].type != VAL_INT || args[1].type != VAL_STR) { current_rt->error = strdup("tls_write() expects (fd: Int, data: String)"); return value_bool(false); } |
1024 | 0 | char *err = NULL; |
1025 | 0 | bool ok = net_tls_write((int)args[0].as.int_val, args[1].as.str_val, strlen(args[1].as.str_val), &err); |
1026 | 0 | if (err) { current_rt->error = err; return value_bool(false); } |
1027 | 0 | return value_bool(ok); |
1028 | 0 | } |
1029 | 0 | static LatValue native_tls_close(LatValue *args, int ac) { |
1030 | 0 | if (ac != 1 || args[0].type != VAL_INT) { current_rt->error = strdup("tls_close() expects (fd: Int)"); return value_unit(); } |
1031 | 0 | net_tls_close((int)args[0].as.int_val); return value_unit(); |
1032 | 0 | } |
1033 | 2 | static LatValue native_tls_available(LatValue *args, int ac) { |
1034 | 2 | (void)args; (void)ac; return value_bool(net_tls_available()); |
1035 | 2 | } |
1036 | | |
1037 | | /* ── HTTP natives ── */ |
1038 | | |
1039 | 0 | static LatValue vm_build_http_response(HttpResponse *resp) { |
1040 | 0 | LatValue map = value_map_new(); |
1041 | 0 | LatValue st = value_int(resp->status_code); |
1042 | 0 | lat_map_set(map.as.map.map, "status", &st); |
1043 | 0 | LatValue bd = value_string(resp->body ? resp->body : ""); |
1044 | 0 | lat_map_set(map.as.map.map, "body", &bd); |
1045 | 0 | LatValue hdr = value_map_new(); |
1046 | 0 | for (size_t i = 0; i < resp->header_count; i++) { |
1047 | 0 | LatValue hv = value_string(resp->header_values[i]); |
1048 | 0 | lat_map_set(hdr.as.map.map, resp->header_keys[i], &hv); |
1049 | 0 | } |
1050 | 0 | lat_map_set(map.as.map.map, "headers", &hdr); |
1051 | 0 | http_response_free(resp); |
1052 | 0 | return map; |
1053 | 0 | } |
1054 | 6 | static LatValue native_http_get(LatValue *args, int ac) { |
1055 | 6 | if (ac != 1 || args[0].type != VAL_STR) { |
1056 | 4 | current_rt->error = strdup("http_get() expects (url: String)"); |
1057 | 4 | return value_nil(); |
1058 | 4 | } |
1059 | 2 | HttpRequest req = {0}; req.method = "GET"; req.url = args[0].as.str_val; |
1060 | 2 | char *err = NULL; |
1061 | 2 | HttpResponse *resp = http_execute(&req, &err); |
1062 | 2 | if (!resp) { current_rt->error = err ? err : strdup("http_get: request failed"); return value_nil(); } |
1063 | 0 | return vm_build_http_response(resp); |
1064 | 2 | } |
1065 | 4 | static LatValue native_http_post(LatValue *args, int ac) { |
1066 | 4 | if (ac < 1 || ac > 2 || args[0].type != VAL_STR) { |
1067 | 2 | current_rt->error = strdup("http_post() expects (url: String, options?: Map)"); |
1068 | 2 | return value_nil(); |
1069 | 2 | } |
1070 | 2 | HttpRequest req = {0}; req.method = "POST"; req.url = args[0].as.str_val; |
1071 | 2 | if (ac >= 2 && args[1].type == VAL_STR) { |
1072 | 0 | req.body = args[1].as.str_val; req.body_len = strlen(args[1].as.str_val); |
1073 | 2 | } else if (ac >= 2 && args[1].type == VAL_MAP) { |
1074 | 0 | LatValue *bv = lat_map_get(args[1].as.map.map, "body"); |
1075 | 0 | if (bv && bv->type == VAL_STR) { req.body = bv->as.str_val; req.body_len = strlen(bv->as.str_val); } |
1076 | 0 | } |
1077 | 2 | char *err = NULL; |
1078 | 2 | HttpResponse *resp = http_execute(&req, &err); |
1079 | 2 | if (!resp) { current_rt->error = err ? err : strdup("http_post: request failed"); return value_nil(); } |
1080 | 0 | return vm_build_http_response(resp); |
1081 | 2 | } |
1082 | 6 | static LatValue native_http_request(LatValue *args, int ac) { |
1083 | 6 | if (ac < 2 || ac > 3 || args[0].type != VAL_STR || args[1].type != VAL_STR) { |
1084 | 4 | current_rt->error = strdup("http_request() expects (method: String, url: String, options?: Map)"); |
1085 | 4 | return value_nil(); |
1086 | 4 | } |
1087 | 2 | HttpRequest req = {0}; |
1088 | 2 | req.method = args[0].as.str_val; |
1089 | 2 | req.url = args[1].as.str_val; |
1090 | 2 | if (ac == 3 && args[2].type == VAL_MAP) { |
1091 | 0 | LatValue *bv = lat_map_get(args[2].as.map.map, "body"); |
1092 | 0 | if (bv && bv->type == VAL_STR) { req.body = bv->as.str_val; req.body_len = strlen(bv->as.str_val); } |
1093 | 0 | } |
1094 | 2 | char *err = NULL; |
1095 | 2 | HttpResponse *resp = http_execute(&req, &err); |
1096 | 2 | if (!resp) { current_rt->error = err ? err : strdup("http_request: request failed"); return value_nil(); } |
1097 | 0 | return vm_build_http_response(resp); |
1098 | 2 | } |
1099 | | |
1100 | | /* ── JSON/TOML/YAML natives ── */ |
1101 | | |
1102 | 33 | static LatValue native_json_parse(LatValue *args, int ac) { |
1103 | 33 | if (ac != 1 || args[0].type != VAL_STR) { |
1104 | 0 | current_rt->error = strdup("json_parse: expected (str: Str)"); |
1105 | 0 | return value_nil(); |
1106 | 0 | } |
1107 | 33 | char *err = NULL; |
1108 | 33 | LatValue r = json_parse(args[0].as.str_val, &err); |
1109 | 33 | if (err) { current_rt->error = err; return value_nil(); } |
1110 | 31 | return r; |
1111 | 33 | } |
1112 | 16 | static LatValue native_json_stringify(LatValue *args, int ac) { |
1113 | 16 | if (ac != 1) { |
1114 | 2 | current_rt->error = strdup("json_stringify: expected 1 argument"); |
1115 | 2 | return value_nil(); |
1116 | 2 | } |
1117 | 14 | char *err = NULL; |
1118 | 14 | char *r = json_stringify(&args[0], &err); |
1119 | 14 | if (err) { current_rt->error = err; return value_nil(); } |
1120 | 14 | return value_string_owned(r); |
1121 | 14 | } |
1122 | 12 | static LatValue native_toml_parse(LatValue *args, int ac) { |
1123 | 12 | if (ac != 1 || args[0].type != VAL_STR) { |
1124 | 2 | current_rt->error = strdup("toml_parse() expects (String)"); |
1125 | 2 | return value_nil(); |
1126 | 2 | } |
1127 | 10 | char *err = NULL; |
1128 | 10 | LatValue r = toml_ops_parse(args[0].as.str_val, &err); |
1129 | 10 | if (err) { current_rt->error = err; return value_nil(); } |
1130 | 10 | return r; |
1131 | 10 | } |
1132 | 4 | static LatValue native_toml_stringify(LatValue *args, int ac) { |
1133 | 4 | if (ac != 1) { |
1134 | 0 | current_rt->error = strdup("toml_stringify: expected 1 argument"); |
1135 | 0 | return value_nil(); |
1136 | 0 | } |
1137 | 4 | char *err = NULL; |
1138 | 4 | char *r = toml_ops_stringify(&args[0], &err); |
1139 | 4 | if (err) { current_rt->error = err; return value_nil(); } |
1140 | 2 | if (!r) return value_nil(); |
1141 | 2 | return value_string_owned(r); |
1142 | 2 | } |
1143 | 14 | static LatValue native_yaml_parse(LatValue *args, int ac) { |
1144 | 14 | if (ac != 1 || args[0].type != VAL_STR) { |
1145 | 2 | current_rt->error = strdup("yaml_parse() expects (String)"); |
1146 | 2 | return value_nil(); |
1147 | 2 | } |
1148 | 12 | char *err = NULL; |
1149 | 12 | LatValue r = yaml_ops_parse(args[0].as.str_val, &err); |
1150 | 12 | if (err) { current_rt->error = err; return value_nil(); } |
1151 | 12 | return r; |
1152 | 12 | } |
1153 | 4 | static LatValue native_yaml_stringify(LatValue *args, int ac) { |
1154 | 4 | if (ac != 1) { |
1155 | 0 | current_rt->error = strdup("yaml_stringify: expected 1 argument"); |
1156 | 0 | return value_nil(); |
1157 | 0 | } |
1158 | 4 | if (args[0].type != VAL_MAP && args[0].type != VAL_ARRAY) { |
1159 | 2 | current_rt->error = strdup("yaml_stringify: value must be a Map or Array"); |
1160 | 2 | return value_nil(); |
1161 | 2 | } |
1162 | 2 | char *err = NULL; |
1163 | 2 | char *r = yaml_ops_stringify(&args[0], &err); |
1164 | 2 | if (err) { current_rt->error = err; return value_nil(); } |
1165 | 2 | if (!r) return value_nil(); |
1166 | 2 | return value_string_owned(r); |
1167 | 2 | } |
1168 | | |
1169 | | /* ── Crypto natives ── */ |
1170 | | |
1171 | 6 | static LatValue native_sha256(LatValue *args, int ac) { |
1172 | 6 | if (ac != 1 || args[0].type != VAL_STR) { |
1173 | 2 | current_rt->error = strdup("sha256: expected (str: Str)"); |
1174 | 2 | return value_nil(); |
1175 | 2 | } |
1176 | 4 | char *err = NULL; |
1177 | 4 | char *r = crypto_sha256(args[0].as.str_val, strlen(args[0].as.str_val), &err); |
1178 | 4 | if (err) { current_rt->error = err; return value_nil(); } |
1179 | 4 | return value_string_owned(r); |
1180 | 4 | } |
1181 | 6 | static LatValue native_md5(LatValue *args, int ac) { |
1182 | 6 | if (ac != 1 || args[0].type != VAL_STR) { |
1183 | 2 | current_rt->error = strdup("md5: expected (str: Str)"); |
1184 | 2 | return value_nil(); |
1185 | 2 | } |
1186 | 4 | char *err = NULL; |
1187 | 4 | char *r = crypto_md5(args[0].as.str_val, strlen(args[0].as.str_val), &err); |
1188 | 4 | if (err) { current_rt->error = err; return value_nil(); } |
1189 | 4 | return value_string_owned(r); |
1190 | 4 | } |
1191 | 18 | static LatValue native_base64_encode(LatValue *args, int ac) { |
1192 | 18 | if (ac != 1 || args[0].type != VAL_STR) { |
1193 | 2 | current_rt->error = strdup("base64_encode: expected (str: Str)"); |
1194 | 2 | return value_nil(); |
1195 | 2 | } |
1196 | 16 | return value_string_owned(crypto_base64_encode(args[0].as.str_val, strlen(args[0].as.str_val))); |
1197 | 18 | } |
1198 | 12 | static LatValue native_base64_decode(LatValue *args, int ac) { |
1199 | 12 | if (ac != 1 || args[0].type != VAL_STR) { |
1200 | 2 | current_rt->error = strdup("base64_decode: expected (str: Str)"); |
1201 | 2 | return value_nil(); |
1202 | 2 | } |
1203 | 10 | char *err = NULL; size_t dl = 0; |
1204 | 10 | char *r = crypto_base64_decode(args[0].as.str_val, strlen(args[0].as.str_val), &dl, &err); |
1205 | 10 | if (err) { current_rt->error = err; return value_nil(); } |
1206 | 8 | return value_string_owned(r); |
1207 | 10 | } |
1208 | | |
1209 | | /* ── Regex natives ── */ |
1210 | | |
1211 | 8 | static LatValue native_regex_match(LatValue *args, int ac) { |
1212 | 8 | if (ac != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { |
1213 | 0 | current_rt->error = strdup("regex_match: expected (pattern: Str, input: Str)"); |
1214 | 0 | return value_bool(false); |
1215 | 0 | } |
1216 | 8 | char *err = NULL; |
1217 | 8 | LatValue r = regex_match(args[0].as.str_val, args[1].as.str_val, &err); |
1218 | 8 | if (err) { current_rt->error = err; return value_bool(false); } |
1219 | 6 | return r; |
1220 | 8 | } |
1221 | 9 | static LatValue native_regex_find_all(LatValue *args, int ac) { |
1222 | 9 | if (ac != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { |
1223 | 0 | current_rt->error = strdup("regex_find_all: expected (pattern: Str, input: Str)"); |
1224 | 0 | return value_array(NULL, 0); |
1225 | 0 | } |
1226 | 9 | char *err = NULL; |
1227 | 9 | LatValue r = regex_find_all(args[0].as.str_val, args[1].as.str_val, &err); |
1228 | 9 | if (err) { current_rt->error = err; return value_array(NULL, 0); } |
1229 | 9 | return r; |
1230 | 9 | } |
1231 | 8 | static LatValue native_regex_replace(LatValue *args, int ac) { |
1232 | 8 | if (ac != 3 || args[0].type != VAL_STR || args[1].type != VAL_STR || args[2].type != VAL_STR) |
1233 | 0 | return value_nil(); |
1234 | 8 | char *err = NULL; |
1235 | 8 | char *r = regex_replace(args[0].as.str_val, args[1].as.str_val, args[2].as.str_val, &err); |
1236 | 8 | if (err) { current_rt->error = err; return value_nil(); } |
1237 | 8 | if (!r) return value_nil(); |
1238 | 8 | return value_string_owned(r); |
1239 | 8 | } |
1240 | | |
1241 | | /* ── Time/DateTime natives ── */ |
1242 | | |
1243 | 37 | static LatValue native_time(LatValue *args, int ac) { |
1244 | 37 | (void)args; |
1245 | 37 | if (ac != 0) { current_rt->error = strdup("time() expects no arguments"); return value_int(0); } |
1246 | 35 | return value_int(time_now_ms()); |
1247 | 37 | } |
1248 | 4 | static LatValue native_sleep(LatValue *args, int ac) { |
1249 | 4 | if (ac != 1 || args[0].type != VAL_INT) { current_rt->error = strdup("sleep() expects (ms: Int)"); return value_unit(); } |
1250 | 2 | char *err = NULL; time_sleep_ms(args[0].as.int_val, &err); |
1251 | 2 | if (err) { current_rt->error = err; return value_unit(); } |
1252 | 2 | return value_unit(); |
1253 | 2 | } |
1254 | 10 | static LatValue native_time_format(LatValue *args, int ac) { |
1255 | 10 | if (ac != 2 || args[0].type != VAL_INT || args[1].type != VAL_STR) { |
1256 | 2 | current_rt->error = strdup("time_format: expected (timestamp: Int, format: Str)"); |
1257 | 2 | return value_nil(); |
1258 | 2 | } |
1259 | 8 | char *err = NULL; |
1260 | 8 | char *r = datetime_format(args[0].as.int_val, args[1].as.str_val, &err); |
1261 | 8 | if (err) { current_rt->error = err; return value_nil(); } |
1262 | 8 | return value_string_owned(r); |
1263 | 8 | } |
1264 | 10 | static LatValue native_time_parse(LatValue *args, int ac) { |
1265 | 10 | if (ac != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { |
1266 | 2 | current_rt->error = strdup("time_parse: expected (str: Str, format: Str)"); |
1267 | 2 | return value_nil(); |
1268 | 2 | } |
1269 | 8 | char *err = NULL; |
1270 | 8 | int64_t r = datetime_parse(args[0].as.str_val, args[1].as.str_val, &err); |
1271 | 8 | if (err) { current_rt->error = err; return value_nil(); } |
1272 | 6 | return value_int(r); |
1273 | 8 | } |
1274 | | |
1275 | | /* ── Environment natives ── */ |
1276 | | |
1277 | 8 | static LatValue native_env(LatValue *args, int ac) { |
1278 | 8 | if (ac != 1 || args[0].type != VAL_STR) { current_rt->error = strdup("env() expects (key: String)"); return value_unit(); } |
1279 | 6 | char *val = envvar_get(args[0].as.str_val); |
1280 | 6 | if (!val) return value_unit(); |
1281 | 4 | return value_string_owned(val); |
1282 | 6 | } |
1283 | 6 | static LatValue native_env_set(LatValue *args, int ac) { |
1284 | 6 | if (ac != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { |
1285 | 2 | current_rt->error = strdup("env_set: expected (key: Str, value: Str)"); |
1286 | 2 | return value_bool(false); |
1287 | 2 | } |
1288 | 4 | char *err = NULL; |
1289 | 4 | bool ok = envvar_set(args[0].as.str_val, args[1].as.str_val, &err); |
1290 | 4 | if (err) { current_rt->error = err; return value_bool(false); } |
1291 | 4 | return value_bool(ok); |
1292 | 4 | } |
1293 | 2 | static LatValue native_env_keys(LatValue *args, int ac) { |
1294 | 2 | (void)args; (void)ac; |
1295 | 2 | char **keys = NULL; size_t count = 0; |
1296 | 2 | envvar_keys(&keys, &count); |
1297 | 2 | LatValue *elems = malloc((count > 0 ? count : 1) * sizeof(LatValue)); |
1298 | 152 | for (size_t i = 0; i < count; i++) { elems[i] = value_string(keys[i]); free(keys[i]); } |
1299 | 2 | free(keys); |
1300 | 2 | LatValue r = value_array(elems, count); free(elems); |
1301 | 2 | return r; |
1302 | 2 | } |
1303 | | |
1304 | | /* ── Process natives ── */ |
1305 | | |
1306 | 2 | static LatValue native_cwd(LatValue *args, int ac) { |
1307 | 2 | (void)args; (void)ac; |
1308 | 2 | char *err = NULL; char *r = process_cwd(&err); |
1309 | 2 | if (err) { current_rt->error = err; return value_nil(); } |
1310 | 2 | if (!r) return value_nil(); |
1311 | 2 | return value_string_owned(r); |
1312 | 2 | } |
1313 | 2 | static LatValue native_exec_cmd(LatValue *args, int ac) { |
1314 | 2 | if (ac != 1 || args[0].type != VAL_STR) return value_nil(); |
1315 | 2 | char *err = NULL; |
1316 | 2 | LatValue r = process_exec(args[0].as.str_val, &err); |
1317 | 2 | if (err) { current_rt->error = err; return value_nil(); } |
1318 | 2 | return r; |
1319 | 2 | } |
1320 | 6 | static LatValue native_shell(LatValue *args, int ac) { |
1321 | 6 | if (ac != 1 || args[0].type != VAL_STR) return value_nil(); |
1322 | 6 | char *err = NULL; |
1323 | 6 | LatValue r = process_shell(args[0].as.str_val, &err); |
1324 | 6 | if (err) { current_rt->error = err; return value_nil(); } |
1325 | 6 | return r; |
1326 | 6 | } |
1327 | 4 | static LatValue native_platform(LatValue *args, int ac) { |
1328 | 4 | (void)args; (void)ac; return value_string(process_platform()); |
1329 | 4 | } |
1330 | 2 | static LatValue native_hostname(LatValue *args, int ac) { |
1331 | 2 | (void)args; (void)ac; |
1332 | 2 | char *err = NULL; char *r = process_hostname(&err); |
1333 | 2 | if (err) { current_rt->error = err; return value_nil(); } |
1334 | 2 | if (!r) return value_nil(); |
1335 | 2 | return value_string_owned(r); |
1336 | 2 | } |
1337 | 2 | static LatValue native_pid(LatValue *args, int ac) { |
1338 | 2 | (void)args; (void)ac; return value_int(process_pid()); |
1339 | 2 | } |
1340 | | |
1341 | | /* ── Type/utility natives ── */ |
1342 | | |
1343 | 0 | static LatValue native_to_int(LatValue *args, int ac) { |
1344 | 0 | if (ac != 1) return value_nil(); |
1345 | 0 | char *err = NULL; |
1346 | 0 | LatValue r = type_to_int(&args[0], &err); |
1347 | 0 | if (err) { current_rt->error = err; return value_nil(); } |
1348 | 0 | return r; |
1349 | 0 | } |
1350 | 0 | static LatValue native_to_float(LatValue *args, int ac) { |
1351 | 0 | if (ac != 1) return value_nil(); |
1352 | 0 | char *err = NULL; |
1353 | 0 | LatValue r = type_to_float(&args[0], &err); |
1354 | 0 | if (err) { current_rt->error = err; return value_nil(); } |
1355 | 0 | return r; |
1356 | 0 | } |
1357 | 2 | static LatValue native_struct_name(LatValue *args, int ac) { |
1358 | 2 | if (ac != 1 || args[0].type != VAL_STRUCT) return value_nil(); |
1359 | 2 | return value_string(args[0].as.strct.name); |
1360 | 2 | } |
1361 | 2 | static LatValue native_struct_fields(LatValue *args, int ac) { |
1362 | 2 | if (ac != 1 || args[0].type != VAL_STRUCT) return value_array(NULL, 0); |
1363 | 2 | size_t fc = args[0].as.strct.field_count; |
1364 | 2 | LatValue *elems = malloc((fc > 0 ? fc : 1) * sizeof(LatValue)); |
1365 | 6 | for (size_t i = 0; i < fc; i++) |
1366 | 4 | elems[i] = value_string(args[0].as.strct.field_names[i]); |
1367 | 2 | LatValue r = value_array(elems, fc); free(elems); |
1368 | 2 | return r; |
1369 | 2 | } |
1370 | 4 | static LatValue native_struct_to_map(LatValue *args, int ac) { |
1371 | 4 | if (ac != 1 || args[0].type != VAL_STRUCT) return value_nil(); |
1372 | 4 | LatValue map = value_map_new(); |
1373 | 12 | for (size_t i = 0; i < args[0].as.strct.field_count; i++) { |
1374 | 8 | LatValue v = value_deep_clone(&args[0].as.strct.field_values[i]); |
1375 | 8 | lat_map_set(map.as.map.map, args[0].as.strct.field_names[i], &v); |
1376 | 8 | } |
1377 | 4 | return map; |
1378 | 4 | } |
1379 | 24 | static LatValue native_repr(LatValue *args, int ac) { |
1380 | 24 | if (ac != 1) return value_nil(); |
1381 | | /* Check for custom repr closure on structs */ |
1382 | 24 | if (args[0].type == VAL_STRUCT) { |
1383 | 16 | for (size_t i = 0; i < args[0].as.strct.field_count; i++) { |
1384 | 14 | if (strcmp(args[0].as.strct.field_names[i], "repr") == 0 && |
1385 | 14 | args[0].as.strct.field_values[i].type == VAL_CLOSURE) { |
1386 | 4 | LatValue self = value_deep_clone(&args[0]); |
1387 | 4 | LatValue result = current_rt->call_closure(current_rt->active_vm, &args[0].as.strct.field_values[i], &self, 1); |
1388 | 4 | value_free(&self); |
1389 | 4 | if (result.type == VAL_STR) return result; |
1390 | 2 | value_free(&result); |
1391 | 2 | break; /* fall through to default */ |
1392 | 4 | } |
1393 | 14 | } |
1394 | 6 | } |
1395 | 22 | return value_string_owned(value_repr(&args[0])); |
1396 | 24 | } |
1397 | 52 | static LatValue native_format(LatValue *args, int ac) { |
1398 | 52 | if (ac < 1 || args[0].type != VAL_STR) { |
1399 | 2 | current_rt->error = strdup("format: expected (fmt: Str, ...)"); |
1400 | 2 | return value_nil(); |
1401 | 2 | } |
1402 | 50 | char *err = NULL; |
1403 | 50 | char *r = format_string(args[0].as.str_val, args + 1, (size_t)(ac - 1), &err); |
1404 | 50 | if (err) { current_rt->error = err; return value_nil(); } |
1405 | 48 | return value_string_owned(r); |
1406 | 50 | } |
1407 | 14 | static LatValue native_range(LatValue *args, int ac) { |
1408 | 14 | if (ac < 2 || ac > 3 || args[0].type != VAL_INT || args[1].type != VAL_INT) { |
1409 | 0 | current_rt->error = strdup("range() expects (start: Int, end: Int, step?: Int)"); |
1410 | 0 | return value_array(NULL, 0); |
1411 | 0 | } |
1412 | 14 | int64_t rstart = args[0].as.int_val, rend = args[1].as.int_val; |
1413 | 14 | int64_t rstep = (rstart <= rend) ? 1 : -1; |
1414 | 14 | if (ac == 3) { |
1415 | 8 | if (args[2].type != VAL_INT) { current_rt->error = strdup("range() step must be Int"); return value_array(NULL, 0); } |
1416 | 8 | rstep = args[2].as.int_val; |
1417 | 8 | } |
1418 | 14 | if (rstep == 0) { current_rt->error = strdup("range() step cannot be zero"); return value_array(NULL, 0); } |
1419 | 12 | size_t rcount = 0; |
1420 | 12 | if (rstep > 0 && rstart < rend) |
1421 | 4 | rcount = (size_t)((rend - rstart + rstep - 1) / rstep); |
1422 | 8 | else if (rstep < 0 && rstart > rend) |
1423 | 4 | rcount = (size_t)((rstart - rend + (-rstep) - 1) / (-rstep)); |
1424 | 12 | LatValue *relems = malloc((rcount > 0 ? rcount : 1) * sizeof(LatValue)); |
1425 | 12 | int64_t rcur = rstart; |
1426 | 50 | for (size_t i = 0; i < rcount; i++) { relems[i] = value_int(rcur); rcur += rstep; } |
1427 | 12 | LatValue r = value_array(relems, rcount); free(relems); |
1428 | 12 | return r; |
1429 | 14 | } |
1430 | 4 | static LatValue native_print_raw(LatValue *args, int ac) { |
1431 | 8 | for (int i = 0; i < ac; i++) { |
1432 | 4 | if (i > 0) printf(" "); |
1433 | 4 | if (args[i].type == VAL_STR) printf("%s", args[i].as.str_val); |
1434 | 0 | else { char *s = value_display(&args[i]); printf("%s", s); free(s); } |
1435 | 4 | } |
1436 | 4 | fflush(stdout); return value_unit(); |
1437 | 4 | } |
1438 | 2 | static LatValue native_eprint(LatValue *args, int ac) { |
1439 | 4 | for (int i = 0; i < ac; i++) { |
1440 | 2 | if (i > 0) fprintf(stderr, " "); |
1441 | 2 | if (args[i].type == VAL_STR) fprintf(stderr, "%s", args[i].as.str_val); |
1442 | 0 | else { char *s = value_display(&args[i]); fprintf(stderr, "%s", s); free(s); } |
1443 | 2 | } |
1444 | 2 | fprintf(stderr, "\n"); return value_unit(); |
1445 | 2 | } |
1446 | 4 | static LatValue native_identity(LatValue *args, int ac) { |
1447 | 4 | if (ac != 1) return value_nil(); |
1448 | 4 | return value_deep_clone(&args[0]); |
1449 | 4 | } |
1450 | 4 | static LatValue native_debug_assert(LatValue *args, int ac) { |
1451 | 4 | if (ac < 1) return value_unit(); |
1452 | 4 | bool ok = (args[0].type == VAL_BOOL) ? args[0].as.bool_val : |
1453 | 4 | (args[0].type == VAL_INT) ? args[0].as.int_val != 0 : |
1454 | 0 | args[0].type != VAL_NIL; |
1455 | 4 | if (!ok) { |
1456 | 2 | const char *msg = (ac >= 2 && args[1].type == VAL_STR) ? args[1].as.str_val : "debug assertion failed"; |
1457 | 2 | if (current_rt) { |
1458 | 2 | char *err = NULL; |
1459 | 2 | (void)asprintf(&err, "debug assertion failed: %s", msg); |
1460 | 2 | current_rt->error = err; |
1461 | 2 | } else { |
1462 | 0 | fprintf(stderr, "debug assertion failed: %s\n", msg); |
1463 | 0 | exit(1); |
1464 | 0 | } |
1465 | 2 | } |
1466 | 4 | return value_unit(); |
1467 | 4 | } |
1468 | | |
1469 | 2 | static LatValue native_panic(LatValue *args, int ac) { |
1470 | 2 | const char *msg = (ac >= 1 && args[0].type == VAL_STR) ? args[0].as.str_val : "panic"; |
1471 | 2 | current_rt->error = strdup(msg); |
1472 | 2 | return value_unit(); |
1473 | 2 | } |
1474 | | |
1475 | | /* Native require: load and execute a file in the global scope (no isolation) */ |
1476 | 9 | static LatValue native_require(LatValue *args, int arg_count) { |
1477 | 9 | if (arg_count < 1 || args[0].type != VAL_STR) { |
1478 | 0 | current_rt->error = strdup("require: expected a string argument"); |
1479 | 0 | return value_bool(false); |
1480 | 0 | } |
1481 | 9 | const char *raw_path = args[0].as.str_val; |
1482 | | |
1483 | | /* Resolve file path: append .lat if not present */ |
1484 | 9 | size_t plen = strlen(raw_path); |
1485 | 9 | char *file_path; |
1486 | 9 | if (plen >= 4 && strcmp(raw_path + plen - 4, ".lat") == 0) { |
1487 | 2 | file_path = strdup(raw_path); |
1488 | 7 | } else { |
1489 | 7 | file_path = malloc(plen + 5); |
1490 | 7 | memcpy(file_path, raw_path, plen); |
1491 | 7 | memcpy(file_path + plen, ".lat", 5); |
1492 | 7 | } |
1493 | | |
1494 | | /* Resolve to absolute path: try CWD first, then script_dir */ |
1495 | 9 | char resolved[PATH_MAX]; |
1496 | 9 | bool found = (realpath(file_path, resolved) != NULL); |
1497 | 9 | if (!found && current_rt->script_dir && file_path[0] != '/') { |
1498 | 0 | char script_rel[PATH_MAX]; |
1499 | 0 | snprintf(script_rel, sizeof(script_rel), "%s/%s", |
1500 | 0 | current_rt->script_dir, file_path); |
1501 | 0 | found = (realpath(script_rel, resolved) != NULL); |
1502 | 0 | } |
1503 | 9 | if (!found) { |
1504 | 1 | (void)asprintf(¤t_rt->error, "require: cannot find '%s'", raw_path); |
1505 | 1 | free(file_path); |
1506 | 1 | return value_bool(false); |
1507 | 1 | } |
1508 | 8 | free(file_path); |
1509 | | |
1510 | | /* Dedup: skip if already required */ |
1511 | 8 | if (lat_map_get(¤t_rt->required_files, resolved)) { |
1512 | 2 | return value_bool(true); |
1513 | 2 | } |
1514 | | |
1515 | | /* Mark as loaded before execution (prevents circular requires) */ |
1516 | 8 | bool loaded = true; |
1517 | 6 | lat_map_set(¤t_rt->required_files, resolved, &loaded); |
1518 | | |
1519 | | /* Read the file */ |
1520 | 6 | char *source = builtin_read_file(resolved); |
1521 | 6 | if (!source) { |
1522 | 0 | (void)asprintf(¤t_rt->error, "require: cannot read '%s'", resolved); |
1523 | 0 | return value_bool(false); |
1524 | 0 | } |
1525 | | |
1526 | | /* Lex */ |
1527 | 6 | Lexer req_lex = lexer_new(source); |
1528 | 6 | char *lex_err = NULL; |
1529 | 6 | LatVec req_toks = lexer_tokenize(&req_lex, &lex_err); |
1530 | 6 | free(source); |
1531 | 6 | if (lex_err) { |
1532 | 0 | (void)asprintf(¤t_rt->error, "require '%s': %s", resolved, lex_err); |
1533 | 0 | free(lex_err); |
1534 | 0 | lat_vec_free(&req_toks); |
1535 | 0 | return value_bool(false); |
1536 | 0 | } |
1537 | | |
1538 | | /* Parse */ |
1539 | 6 | Parser req_parser = parser_new(&req_toks); |
1540 | 6 | char *parse_err = NULL; |
1541 | 6 | Program req_prog = parser_parse(&req_parser, &parse_err); |
1542 | 6 | if (parse_err) { |
1543 | 0 | (void)asprintf(¤t_rt->error, "require '%s': %s", resolved, parse_err); |
1544 | 0 | free(parse_err); |
1545 | 0 | program_free(&req_prog); |
1546 | 0 | for (size_t ti = 0; ti < req_toks.len; ti++) |
1547 | 0 | token_free(lat_vec_get(&req_toks, ti)); |
1548 | 0 | lat_vec_free(&req_toks); |
1549 | 0 | return value_bool(false); |
1550 | 0 | } |
1551 | | |
1552 | | /* Dispatch to the active VM backend */ |
1553 | 6 | if (current_rt->backend == RT_BACKEND_REG_VM) { |
1554 | 0 | char *comp_err = NULL; |
1555 | 0 | RegChunk *rchunk = reg_compile_module(&req_prog, &comp_err); |
1556 | 0 | program_free(&req_prog); |
1557 | 0 | for (size_t ti = 0; ti < req_toks.len; ti++) |
1558 | 0 | token_free(lat_vec_get(&req_toks, ti)); |
1559 | 0 | lat_vec_free(&req_toks); |
1560 | 0 | if (!rchunk) { |
1561 | 0 | (void)asprintf(¤t_rt->error, "require '%s': %s", resolved, |
1562 | 0 | comp_err ? comp_err : "compile error"); |
1563 | 0 | free(comp_err); |
1564 | 0 | return value_bool(false); |
1565 | 0 | } |
1566 | 0 | RegVM *rvm = (RegVM *)current_rt->active_vm; |
1567 | 0 | regvm_track_chunk(rvm, rchunk); |
1568 | 0 | LatValue req_result; |
1569 | 0 | RegVMResult rr = regvm_run(rvm, rchunk, &req_result); |
1570 | 0 | if (rr != REGVM_OK) { |
1571 | 0 | (void)asprintf(¤t_rt->error, "require '%s': runtime error: %s", resolved, |
1572 | 0 | rvm->error ? rvm->error : "(unknown)"); |
1573 | 0 | return value_bool(false); |
1574 | 0 | } |
1575 | 0 | value_free(&req_result); |
1576 | 6 | } else { |
1577 | 6 | char *comp_err = NULL; |
1578 | 6 | Chunk *req_chunk = stack_compile_module(&req_prog, &comp_err); |
1579 | 6 | program_free(&req_prog); |
1580 | 107 | for (size_t ti = 0; ti < req_toks.len; ti++) |
1581 | 101 | token_free(lat_vec_get(&req_toks, ti)); |
1582 | 6 | lat_vec_free(&req_toks); |
1583 | 6 | if (!req_chunk) { |
1584 | 0 | (void)asprintf(¤t_rt->error, "require '%s': %s", resolved, |
1585 | 0 | comp_err ? comp_err : "compile error"); |
1586 | 0 | free(comp_err); |
1587 | 0 | return value_bool(false); |
1588 | 0 | } |
1589 | 6 | StackVM *vm = (StackVM *)current_rt->active_vm; |
1590 | | /* Track the chunk for proper lifetime management */ |
1591 | 6 | if (vm->fn_chunk_count >= vm->fn_chunk_cap) { |
1592 | 5 | vm->fn_chunk_cap = vm->fn_chunk_cap ? vm->fn_chunk_cap * 2 : 8; |
1593 | 5 | vm->fn_chunks = realloc(vm->fn_chunks, |
1594 | 5 | vm->fn_chunk_cap * sizeof(Chunk *)); |
1595 | 5 | } |
1596 | 6 | vm->fn_chunks[vm->fn_chunk_count++] = req_chunk; |
1597 | 6 | LatValue req_result; |
1598 | 6 | StackVMResult req_r = stackvm_run(vm, req_chunk, &req_result); |
1599 | 6 | if (req_r != STACKVM_OK) { |
1600 | 0 | (void)asprintf(¤t_rt->error, "require '%s': runtime error: %s", resolved, |
1601 | 0 | vm->error ? vm->error : "(unknown)"); |
1602 | 0 | return value_bool(false); |
1603 | 0 | } |
1604 | 6 | value_free(&req_result); |
1605 | 6 | } |
1606 | | |
1607 | 6 | return value_bool(true); |
1608 | 6 | } |
1609 | | |
1610 | | /* Native require_ext: load a native extension (.dylib/.so) and return a Map */ |
1611 | 36 | static LatValue native_require_ext(LatValue *args, int arg_count) { |
1612 | 36 | if (arg_count < 1 || args[0].type != VAL_STR) { |
1613 | 4 | current_rt->error = strdup("require_ext: expected a string argument"); |
1614 | 4 | return value_nil(); |
1615 | 4 | } |
1616 | 32 | const char *ext_name = args[0].as.str_val; |
1617 | | |
1618 | | /* Check cache */ |
1619 | 32 | LatValue *cached = (LatValue *)lat_map_get(¤t_rt->loaded_extensions, ext_name); |
1620 | 32 | if (cached) { |
1621 | 0 | return value_deep_clone(cached); |
1622 | 0 | } |
1623 | | |
1624 | | /* Load extension */ |
1625 | 32 | char *ext_err = NULL; |
1626 | 32 | LatValue ext_map = ext_load(NULL, ext_name, &ext_err); |
1627 | 32 | if (ext_err) { |
1628 | 2 | (void)asprintf(¤t_rt->error, "require_ext: %s", ext_err); |
1629 | 2 | free(ext_err); |
1630 | 2 | return value_nil(); |
1631 | 2 | } |
1632 | | |
1633 | | /* Mark extension closures with VM_EXT_MARKER so the StackVM dispatches them |
1634 | | * through ext_call_native() instead of treating native_fn as a Chunk*. */ |
1635 | 30 | if (ext_map.type == VAL_MAP && ext_map.as.map.map) { |
1636 | 510 | for (size_t i = 0; i < ext_map.as.map.map->cap; i++) { |
1637 | 480 | if (ext_map.as.map.map->entries[i].state != MAP_OCCUPIED) continue; |
1638 | 180 | LatValue *v = (LatValue *)ext_map.as.map.map->entries[i].value; |
1639 | 180 | if (v->type == VAL_CLOSURE && v->as.closure.native_fn && !v->as.closure.body) { |
1640 | 180 | v->as.closure.default_values = VM_EXT_MARKER; |
1641 | 180 | } |
1642 | 180 | } |
1643 | 30 | } |
1644 | | |
1645 | | /* Cache the extension */ |
1646 | 30 | LatValue cached_copy = value_deep_clone(&ext_map); |
1647 | 30 | lat_map_set(¤t_rt->loaded_extensions, ext_name, &cached_copy); |
1648 | | |
1649 | 30 | return ext_map; |
1650 | 32 | } |
1651 | | |
1652 | | /* ── Missing native builtins ── */ |
1653 | | |
1654 | 2 | static LatValue native_args(LatValue *args, int arg_count) { |
1655 | 2 | (void)args; (void)arg_count; |
1656 | 2 | int ac = current_rt->prog_argc; |
1657 | 2 | char **av = current_rt->prog_argv; |
1658 | 2 | LatValue *elems = NULL; |
1659 | 2 | if (ac > 0) { |
1660 | 0 | elems = malloc((size_t)ac * sizeof(LatValue)); |
1661 | 0 | for (int i = 0; i < ac; i++) |
1662 | 0 | elems[i] = value_string(av[i]); |
1663 | 0 | } |
1664 | 2 | LatValue arr = value_array(elems, (size_t)ac); |
1665 | 2 | free(elems); |
1666 | 2 | return arr; |
1667 | 2 | } |
1668 | | |
1669 | 6 | static LatValue native_struct_from_map(LatValue *args, int arg_count) { |
1670 | 6 | if (arg_count < 2 || args[0].type != VAL_STR || args[1].type != VAL_MAP) { |
1671 | 0 | current_rt->error = strdup("struct_from_map: expected (name: Str, fields: Map)"); |
1672 | 0 | return value_nil(); |
1673 | 0 | } |
1674 | 6 | const char *sname = args[0].as.str_val; |
1675 | | /* Look up struct field names from env metadata */ |
1676 | 6 | char meta_key[256]; |
1677 | 6 | snprintf(meta_key, sizeof(meta_key), "__struct_%s", sname); |
1678 | 6 | LatValue meta; |
1679 | 6 | if (!env_get(current_rt->env, meta_key, &meta)) { |
1680 | 2 | (void)asprintf(¤t_rt->error, "struct_from_map: unknown struct '%s'", sname); |
1681 | 2 | return value_nil(); |
1682 | 2 | } |
1683 | 4 | if (meta.type != VAL_ARRAY) { value_free(&meta); return value_nil(); } |
1684 | 4 | size_t fc = meta.as.array.len; |
1685 | 4 | char **names = malloc(fc * sizeof(char *)); |
1686 | 4 | LatValue *vals = malloc(fc * sizeof(LatValue)); |
1687 | 12 | for (size_t j = 0; j < fc; j++) { |
1688 | 8 | names[j] = meta.as.array.elems[j].as.str_val; |
1689 | 8 | LatValue *found = (LatValue *)lat_map_get(args[1].as.map.map, names[j]); |
1690 | 8 | vals[j] = found ? value_deep_clone(found) : value_nil(); |
1691 | 8 | } |
1692 | 4 | LatValue st = value_struct(sname, names, vals, fc); |
1693 | 4 | free(names); |
1694 | 4 | free(vals); |
1695 | 4 | value_free(&meta); |
1696 | 4 | return st; |
1697 | 4 | } |
1698 | | |
1699 | 4 | static LatValue native_url_encode(LatValue *args, int arg_count) { |
1700 | 4 | if (arg_count < 1 || args[0].type != VAL_STR) return value_nil(); |
1701 | 4 | const char *src = args[0].as.str_val; |
1702 | 4 | size_t slen = strlen(src); |
1703 | 4 | size_t cap = slen * 3 + 1; |
1704 | 4 | char *out = malloc(cap); |
1705 | 4 | size_t j = 0; |
1706 | 52 | for (size_t i = 0; i < slen; i++) { |
1707 | 48 | unsigned char c = (unsigned char)src[i]; |
1708 | 48 | if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || |
1709 | 48 | (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~') { |
1710 | 40 | out[j++] = (char)c; |
1711 | 40 | } else { |
1712 | 8 | snprintf(out + j, 4, "%%%02X", c); |
1713 | 8 | j += 3; |
1714 | 8 | } |
1715 | 48 | } |
1716 | 4 | out[j] = '\0'; |
1717 | 4 | return value_string_owned(out); |
1718 | 4 | } |
1719 | | |
1720 | 4 | static LatValue native_url_decode(LatValue *args, int arg_count) { |
1721 | 4 | if (arg_count < 1 || args[0].type != VAL_STR) return value_nil(); |
1722 | 4 | const char *src = args[0].as.str_val; |
1723 | 4 | size_t slen = strlen(src); |
1724 | 4 | char *out = malloc(slen + 1); |
1725 | 4 | size_t j = 0; |
1726 | 40 | for (size_t i = 0; i < slen; i++) { |
1727 | 36 | if (src[i] == '%' && i + 2 < slen) { |
1728 | 2 | char hex[3] = { src[i+1], src[i+2], '\0' }; |
1729 | 2 | char *end = NULL; |
1730 | 2 | unsigned long val = strtoul(hex, &end, 16); |
1731 | 2 | if (end == hex + 2) { |
1732 | 2 | out[j++] = (char)val; |
1733 | 2 | i += 2; |
1734 | 2 | } else { |
1735 | 0 | out[j++] = src[i]; |
1736 | 0 | } |
1737 | 34 | } else if (src[i] == '+') { |
1738 | 2 | out[j++] = ' '; |
1739 | 32 | } else { |
1740 | 32 | out[j++] = src[i]; |
1741 | 32 | } |
1742 | 36 | } |
1743 | 4 | out[j] = '\0'; |
1744 | 4 | return value_string_owned(out); |
1745 | 4 | } |
1746 | | |
1747 | 6 | static LatValue native_csv_parse(LatValue *args, int arg_count) { |
1748 | 6 | if (arg_count < 1 || args[0].type != VAL_STR) return value_nil(); |
1749 | 6 | const char *input = args[0].as.str_val; |
1750 | 6 | size_t pos = 0, input_len = strlen(input); |
1751 | 6 | size_t rows_cap = 8, rows_len = 0; |
1752 | 6 | LatValue *rows = malloc(rows_cap * sizeof(LatValue)); |
1753 | | |
1754 | 18 | while (pos < input_len) { |
1755 | 12 | size_t fields_cap = 8, fields_len = 0; |
1756 | 12 | LatValue *fields = malloc(fields_cap * sizeof(LatValue)); |
1757 | 28 | for (;;) { |
1758 | 28 | size_t field_cap = 64, field_len = 0; |
1759 | 28 | char *field = malloc(field_cap); |
1760 | 28 | if (pos < input_len && input[pos] == '"') { |
1761 | 4 | pos++; |
1762 | 34 | for (;;) { |
1763 | 34 | if (pos >= input_len) break; |
1764 | 34 | if (input[pos] == '"') { |
1765 | 4 | if (pos + 1 < input_len && input[pos + 1] == '"') { |
1766 | 0 | if (field_len + 1 >= field_cap) { field_cap *= 2; field = realloc(field, field_cap); } |
1767 | 0 | field[field_len++] = '"'; pos += 2; |
1768 | 4 | } else { pos++; break; } |
1769 | 30 | } else { |
1770 | 30 | if (field_len + 1 >= field_cap) { field_cap *= 2; field = realloc(field, field_cap); } |
1771 | 30 | field[field_len++] = input[pos++]; |
1772 | 30 | } |
1773 | 34 | } |
1774 | 24 | } else { |
1775 | 60 | while (pos < input_len && input[pos] != ',' && input[pos] != '\n' && input[pos] != '\r') { |
1776 | 36 | if (field_len + 1 >= field_cap) { field_cap *= 2; field = realloc(field, field_cap); } |
1777 | 36 | field[field_len++] = input[pos++]; |
1778 | 36 | } |
1779 | 24 | } |
1780 | 28 | field[field_len] = '\0'; |
1781 | 28 | if (fields_len >= fields_cap) { fields_cap *= 2; fields = realloc(fields, fields_cap * sizeof(LatValue)); } |
1782 | 28 | fields[fields_len++] = value_string_owned(field); |
1783 | 28 | if (pos < input_len && input[pos] == ',') { pos++; } else break; |
1784 | 28 | } |
1785 | 12 | if (pos < input_len && input[pos] == '\r') pos++; |
1786 | 12 | if (pos < input_len && input[pos] == '\n') pos++; |
1787 | 12 | LatValue row = value_array(fields, fields_len); |
1788 | 12 | free(fields); |
1789 | 12 | if (rows_len >= rows_cap) { rows_cap *= 2; rows = realloc(rows, rows_cap * sizeof(LatValue)); } |
1790 | 12 | rows[rows_len++] = row; |
1791 | 12 | } |
1792 | 6 | LatValue result = value_array(rows, rows_len); |
1793 | 6 | free(rows); |
1794 | 6 | return result; |
1795 | 6 | } |
1796 | | |
1797 | 4 | static LatValue native_csv_stringify(LatValue *args, int arg_count) { |
1798 | 4 | if (arg_count < 1 || args[0].type != VAL_ARRAY) return value_nil(); |
1799 | 4 | LatValue *data = &args[0]; |
1800 | 4 | size_t out_cap = 256, out_len = 0; |
1801 | 4 | char *out = malloc(out_cap); |
1802 | 12 | for (size_t r = 0; r < data->as.array.len; r++) { |
1803 | 8 | LatValue *row = &data->as.array.elems[r]; |
1804 | 8 | if (row->type != VAL_ARRAY) { free(out); return value_nil(); } |
1805 | 24 | for (size_t c = 0; c < row->as.array.len; c++) { |
1806 | 16 | if (c > 0) { |
1807 | 8 | if (out_len + 1 >= out_cap) { out_cap *= 2; out = realloc(out, out_cap); } |
1808 | 8 | out[out_len++] = ','; |
1809 | 8 | } |
1810 | 16 | char *field_str = value_display(&row->as.array.elems[c]); |
1811 | 16 | size_t flen = strlen(field_str); |
1812 | 16 | bool needs_quote = false; |
1813 | 52 | for (size_t k = 0; k < flen; k++) { |
1814 | 36 | if (field_str[k] == ',' || field_str[k] == '"' || field_str[k] == '\n' || field_str[k] == '\r') { |
1815 | 0 | needs_quote = true; break; |
1816 | 0 | } |
1817 | 36 | } |
1818 | 16 | if (needs_quote) { |
1819 | 0 | size_t extra = 0; |
1820 | 0 | for (size_t k = 0; k < flen; k++) { if (field_str[k] == '"') extra++; } |
1821 | 0 | size_t needed = flen + extra + 2; |
1822 | 0 | while (out_len + needed >= out_cap) { out_cap *= 2; out = realloc(out, out_cap); } |
1823 | 0 | out[out_len++] = '"'; |
1824 | 0 | for (size_t k = 0; k < flen; k++) { |
1825 | 0 | if (field_str[k] == '"') out[out_len++] = '"'; |
1826 | 0 | out[out_len++] = field_str[k]; |
1827 | 0 | } |
1828 | 0 | out[out_len++] = '"'; |
1829 | 16 | } else { |
1830 | 16 | while (out_len + flen >= out_cap) { out_cap *= 2; out = realloc(out, out_cap); } |
1831 | 16 | memcpy(out + out_len, field_str, flen); |
1832 | 16 | out_len += flen; |
1833 | 16 | } |
1834 | 16 | free(field_str); |
1835 | 16 | } |
1836 | 8 | if (out_len + 1 >= out_cap) { out_cap *= 2; out = realloc(out, out_cap); } |
1837 | 8 | out[out_len++] = '\n'; |
1838 | 8 | } |
1839 | 4 | out[out_len] = '\0'; |
1840 | 4 | return value_string_owned(out); |
1841 | 4 | } |
1842 | | |
1843 | 8 | static LatValue native_is_complete(LatValue *args, int arg_count) { |
1844 | 8 | if (arg_count < 1 || args[0].type != VAL_STR) return value_bool(false); |
1845 | 8 | const char *source = args[0].as.str_val; |
1846 | 8 | Lexer lex = lexer_new(source); |
1847 | 8 | char *lex_err = NULL; |
1848 | 8 | LatVec toks = lexer_tokenize(&lex, &lex_err); |
1849 | 8 | if (lex_err) { free(lex_err); lat_vec_free(&toks); return value_bool(false); } |
1850 | 8 | int depth = 0; |
1851 | 46 | for (size_t j = 0; j < toks.len; j++) { |
1852 | 38 | Token *t = lat_vec_get(&toks, j); |
1853 | 38 | switch (t->type) { |
1854 | 8 | case TOK_LBRACE: case TOK_LPAREN: case TOK_LBRACKET: depth++; break; |
1855 | 6 | case TOK_RBRACE: case TOK_RPAREN: case TOK_RBRACKET: depth--; break; |
1856 | 24 | default: break; |
1857 | 38 | } |
1858 | 38 | } |
1859 | 46 | for (size_t j = 0; j < toks.len; j++) token_free(lat_vec_get(&toks, j)); |
1860 | 8 | lat_vec_free(&toks); |
1861 | 8 | return value_bool(depth <= 0); |
1862 | 8 | } |
1863 | | |
1864 | 0 | static LatValue native_float_to_bits(LatValue *args, int arg_count) { |
1865 | 0 | if (arg_count != 1 || args[0].type != VAL_FLOAT) return value_nil(); |
1866 | 0 | double d = args[0].as.float_val; |
1867 | 0 | uint64_t bits; memcpy(&bits, &d, 8); |
1868 | 0 | return value_int((int64_t)bits); |
1869 | 0 | } |
1870 | | |
1871 | 0 | static LatValue native_bits_to_float(LatValue *args, int arg_count) { |
1872 | 0 | if (arg_count != 1 || args[0].type != VAL_INT) return value_nil(); |
1873 | 0 | uint64_t bits = (uint64_t)args[0].as.int_val; |
1874 | 0 | double d; memcpy(&d, &bits, 8); |
1875 | 0 | return value_float(d); |
1876 | 0 | } |
1877 | | |
1878 | 2 | static LatValue native_tokenize(LatValue *args, int arg_count) { |
1879 | 2 | if (arg_count < 1 || args[0].type != VAL_STR) return value_nil(); |
1880 | 2 | const char *source = args[0].as.str_val; |
1881 | 2 | Lexer lex = lexer_new(source); |
1882 | 2 | char *lex_err = NULL; |
1883 | 2 | LatVec toks = lexer_tokenize(&lex, &lex_err); |
1884 | 2 | if (lex_err) { free(lex_err); lat_vec_free(&toks); return value_nil(); } |
1885 | 2 | size_t tok_count = toks.len > 0 ? toks.len - 1 : 0; |
1886 | 2 | LatValue *elems = malloc((tok_count > 0 ? tok_count : 1) * sizeof(LatValue)); |
1887 | 10 | for (size_t j = 0; j < tok_count; j++) { |
1888 | 8 | Token *t = lat_vec_get(&toks, j); |
1889 | 8 | const char *type_str = token_type_name(t->type); |
1890 | 8 | char *text; |
1891 | 8 | if (t->type == TOK_IDENT || t->type == TOK_STRING_LIT || t->type == TOK_MODE_DIRECTIVE || |
1892 | 8 | t->type == TOK_INTERP_START || t->type == TOK_INTERP_MID || t->type == TOK_INTERP_END) { |
1893 | 2 | text = strdup(t->as.str_val); |
1894 | 6 | } else if (t->type == TOK_INT_LIT) { |
1895 | 2 | (void)asprintf(&text, "%lld", (long long)t->as.int_val); |
1896 | 4 | } else if (t->type == TOK_FLOAT_LIT) { |
1897 | 0 | (void)asprintf(&text, "%g", t->as.float_val); |
1898 | 4 | } else { |
1899 | 4 | text = strdup(token_type_name(t->type)); |
1900 | 4 | } |
1901 | 8 | char *fnames[3] = { "type", "text", "line" }; |
1902 | 8 | LatValue fvals[3]; |
1903 | 8 | fvals[0] = value_string(type_str); |
1904 | 8 | fvals[1] = value_string_owned(text); |
1905 | 8 | fvals[2] = value_int((int64_t)t->line); |
1906 | 8 | elems[j] = value_struct("Token", fnames, fvals, 3); |
1907 | 8 | } |
1908 | 12 | for (size_t j = 0; j < toks.len; j++) token_free(lat_vec_get(&toks, j)); |
1909 | 2 | lat_vec_free(&toks); |
1910 | 2 | LatValue arr = value_array(elems, tok_count); |
1911 | 2 | free(elems); |
1912 | 2 | return arr; |
1913 | 2 | } |
1914 | | |
1915 | 26 | static LatValue native_lat_eval(LatValue *args, int arg_count) { |
1916 | 26 | if (arg_count < 1 || args[0].type != VAL_STR) return value_nil(); |
1917 | 26 | const char *source = args[0].as.str_val; |
1918 | 26 | Lexer lex = lexer_new(source); |
1919 | 26 | char *lex_err = NULL; |
1920 | 26 | LatVec toks = lexer_tokenize(&lex, &lex_err); |
1921 | 26 | if (lex_err) { |
1922 | 0 | (void)asprintf(¤t_rt->error, "lat_eval: %s", lex_err); |
1923 | 0 | free(lex_err); lat_vec_free(&toks); |
1924 | 0 | return value_nil(); |
1925 | 0 | } |
1926 | 26 | Parser parser = parser_new(&toks); |
1927 | 26 | char *parse_err = NULL; |
1928 | 26 | Program prog = parser_parse(&parser, &parse_err); |
1929 | 26 | if (parse_err) { |
1930 | 0 | (void)asprintf(¤t_rt->error, "lat_eval: %s", parse_err); |
1931 | 0 | free(parse_err); program_free(&prog); |
1932 | 0 | for (size_t j = 0; j < toks.len; j++) token_free(lat_vec_get(&toks, j)); |
1933 | 0 | lat_vec_free(&toks); |
1934 | 0 | return value_nil(); |
1935 | 0 | } |
1936 | | |
1937 | 26 | if (current_rt->backend == RT_BACKEND_REG_VM) { |
1938 | 13 | char *comp_err = NULL; |
1939 | 13 | RegChunk *rchunk = reg_compile_repl(&prog, &comp_err); |
1940 | 13 | program_free(&prog); |
1941 | 104 | for (size_t j = 0; j < toks.len; j++) token_free(lat_vec_get(&toks, j)); |
1942 | 13 | lat_vec_free(&toks); |
1943 | 13 | if (!rchunk) { |
1944 | 0 | (void)asprintf(¤t_rt->error, "lat_eval: %s", comp_err ? comp_err : "compile error"); |
1945 | 0 | free(comp_err); |
1946 | 0 | return value_nil(); |
1947 | 0 | } |
1948 | 13 | RegVM *rvm = (RegVM *)current_rt->active_vm; |
1949 | 13 | regvm_track_chunk(rvm, rchunk); |
1950 | 13 | LatValue result; |
1951 | 13 | RegVMResult rr = regvm_run(rvm, rchunk, &result); |
1952 | 13 | if (rr != REGVM_OK) return value_nil(); |
1953 | 13 | return result; |
1954 | 13 | } |
1955 | | |
1956 | 13 | char *comp_err = NULL; |
1957 | 13 | Chunk *chunk = stack_compile_repl(&prog, &comp_err); |
1958 | 13 | program_free(&prog); |
1959 | 104 | for (size_t j = 0; j < toks.len; j++) token_free(lat_vec_get(&toks, j)); |
1960 | 13 | lat_vec_free(&toks); |
1961 | 13 | if (!chunk) { |
1962 | 0 | (void)asprintf(¤t_rt->error, "lat_eval: %s", comp_err ? comp_err : "compile error"); |
1963 | 0 | free(comp_err); |
1964 | 0 | return value_nil(); |
1965 | 0 | } |
1966 | 13 | StackVM *vm = (StackVM *)current_rt->active_vm; |
1967 | 13 | if (vm->fn_chunk_count >= vm->fn_chunk_cap) { |
1968 | 6 | vm->fn_chunk_cap = vm->fn_chunk_cap ? vm->fn_chunk_cap * 2 : 8; |
1969 | 6 | vm->fn_chunks = realloc(vm->fn_chunks, vm->fn_chunk_cap * sizeof(Chunk *)); |
1970 | 6 | } |
1971 | 13 | vm->fn_chunks[vm->fn_chunk_count++] = chunk; |
1972 | 13 | LatValue result; |
1973 | 13 | StackVMResult r = stackvm_run(vm, chunk, &result); |
1974 | 13 | if (r != STACKVM_OK) return value_nil(); |
1975 | 13 | return result; |
1976 | 13 | } |
1977 | | |
1978 | 4 | static LatValue native_pipe(LatValue *args, int arg_count) { |
1979 | 4 | if (arg_count < 2) return value_nil(); |
1980 | 4 | (void)0; /* uses dispatch */ |
1981 | 4 | LatValue current = value_deep_clone(&args[0]); |
1982 | 14 | for (int i = 1; i < arg_count; i++) { |
1983 | 10 | if (args[i].type != VAL_CLOSURE) { value_free(¤t); return value_nil(); } |
1984 | 10 | LatValue result = current_rt->call_closure(current_rt->active_vm, &args[i], ¤t, 1); |
1985 | 10 | value_free(¤t); |
1986 | 10 | current = result; |
1987 | 10 | } |
1988 | 4 | return current; |
1989 | 4 | } |
1990 | | |
1991 | 2 | static LatValue native_compose(LatValue *args, int arg_count) { |
1992 | 2 | if (arg_count < 2 || args[0].type != VAL_CLOSURE || args[1].type != VAL_CLOSURE) |
1993 | 0 | return value_nil(); |
1994 | 2 | StackVM *vm = (StackVM *)current_rt->active_vm; |
1995 | | |
1996 | | /* Store f and g in env with unique names so the composed closure can find them */ |
1997 | 2 | static int compose_counter = 0; |
1998 | 2 | char f_name[64], g_name[64]; |
1999 | 2 | snprintf(f_name, sizeof(f_name), "__compose_f_%d", compose_counter); |
2000 | 2 | snprintf(g_name, sizeof(g_name), "__compose_g_%d", compose_counter); |
2001 | 2 | compose_counter++; |
2002 | 2 | env_define(current_rt->env, f_name, value_deep_clone(&args[0])); |
2003 | 2 | env_define(current_rt->env, g_name, value_deep_clone(&args[1])); |
2004 | | |
2005 | | /* Build chunk: GET_GLOBAL f, GET_GLOBAL g, GET_LOCAL 1(x), CALL 1, CALL 1, RETURN */ |
2006 | 2 | Chunk *chunk = chunk_new(); |
2007 | 2 | size_t f_idx = chunk_add_constant(chunk, value_string(f_name)); |
2008 | 2 | size_t g_idx = chunk_add_constant(chunk, value_string(g_name)); |
2009 | 2 | chunk_write(chunk, OP_GET_GLOBAL, 0); |
2010 | 2 | chunk_write(chunk, (uint8_t)f_idx, 0); |
2011 | 2 | chunk_write(chunk, OP_GET_GLOBAL, 0); |
2012 | 2 | chunk_write(chunk, (uint8_t)g_idx, 0); |
2013 | 2 | chunk_write(chunk, OP_GET_LOCAL, 0); |
2014 | 2 | chunk_write(chunk, 1, 0); |
2015 | 2 | chunk_write(chunk, OP_CALL, 0); |
2016 | 2 | chunk_write(chunk, 1, 0); |
2017 | 2 | chunk_write(chunk, OP_CALL, 0); |
2018 | 2 | chunk_write(chunk, 1, 0); |
2019 | 2 | chunk_write(chunk, OP_RETURN, 0); |
2020 | | |
2021 | 2 | if (vm->fn_chunk_count >= vm->fn_chunk_cap) { |
2022 | 1 | vm->fn_chunk_cap = vm->fn_chunk_cap ? vm->fn_chunk_cap * 2 : 8; |
2023 | 1 | vm->fn_chunks = realloc(vm->fn_chunks, vm->fn_chunk_cap * sizeof(Chunk *)); |
2024 | 1 | } |
2025 | 2 | vm->fn_chunks[vm->fn_chunk_count++] = chunk; |
2026 | | |
2027 | | /* Build a compiled closure with 1 parameter */ |
2028 | 2 | char **params = malloc(sizeof(char *)); |
2029 | 2 | params[0] = strdup("x"); |
2030 | 2 | LatValue closure = value_closure(params, 1, NULL, NULL, NULL, false); |
2031 | 2 | closure.as.closure.native_fn = (void *)chunk; |
2032 | 2 | free(params); |
2033 | 2 | return closure; |
2034 | 2 | } |
2035 | | |
2036 | | /* ── Bytecode compilation/loading builtins ── */ |
2037 | | |
2038 | 0 | static char *latc_read_file(const char *path) { |
2039 | 0 | FILE *f = fopen(path, "r"); |
2040 | 0 | if (!f) return NULL; |
2041 | 0 | fseek(f, 0, SEEK_END); |
2042 | 0 | long len = ftell(f); |
2043 | 0 | fseek(f, 0, SEEK_SET); |
2044 | 0 | if (len < 0) { fclose(f); return NULL; } |
2045 | 0 | char *buf = malloc((size_t)len + 1); |
2046 | 0 | if (!buf) { fclose(f); return NULL; } |
2047 | 0 | size_t n = fread(buf, 1, (size_t)len, f); |
2048 | 0 | buf[n] = '\0'; |
2049 | 0 | fclose(f); |
2050 | 0 | return buf; |
2051 | 0 | } |
2052 | | |
2053 | 0 | static LatValue native_compile_file(LatValue *args, int ac) { |
2054 | 0 | if (ac != 1 || args[0].type != VAL_STR) return value_nil(); |
2055 | 0 | const char *path = args[0].as.str_val; |
2056 | |
|
2057 | 0 | char *source = latc_read_file(path); |
2058 | 0 | if (!source) return value_nil(); |
2059 | | |
2060 | 0 | Lexer lex = lexer_new(source); |
2061 | 0 | char *lex_err = NULL; |
2062 | 0 | LatVec tokens = lexer_tokenize(&lex, &lex_err); |
2063 | 0 | if (lex_err) { |
2064 | 0 | free(lex_err); |
2065 | 0 | lat_vec_free(&tokens); |
2066 | 0 | free(source); |
2067 | 0 | return value_nil(); |
2068 | 0 | } |
2069 | | |
2070 | 0 | Parser parser = parser_new(&tokens); |
2071 | 0 | char *parse_err = NULL; |
2072 | 0 | Program prog = parser_parse(&parser, &parse_err); |
2073 | 0 | if (parse_err) { |
2074 | 0 | free(parse_err); |
2075 | 0 | program_free(&prog); |
2076 | 0 | for (size_t i = 0; i < tokens.len; i++) |
2077 | 0 | token_free(lat_vec_get(&tokens, i)); |
2078 | 0 | lat_vec_free(&tokens); |
2079 | 0 | free(source); |
2080 | 0 | return value_nil(); |
2081 | 0 | } |
2082 | | |
2083 | 0 | char *comp_err = NULL; |
2084 | 0 | Chunk *chunk = stack_compile(&prog, &comp_err); |
2085 | 0 | program_free(&prog); |
2086 | 0 | for (size_t i = 0; i < tokens.len; i++) |
2087 | 0 | token_free(lat_vec_get(&tokens, i)); |
2088 | 0 | lat_vec_free(&tokens); |
2089 | 0 | free(source); |
2090 | |
|
2091 | 0 | if (!chunk) { |
2092 | 0 | free(comp_err); |
2093 | 0 | return value_nil(); |
2094 | 0 | } |
2095 | | |
2096 | 0 | size_t buf_len; |
2097 | 0 | uint8_t *buf = chunk_serialize(chunk, &buf_len); |
2098 | 0 | chunk_free(chunk); |
2099 | |
|
2100 | 0 | LatValue result = value_buffer(buf, buf_len); |
2101 | 0 | free(buf); |
2102 | 0 | return result; |
2103 | 0 | } |
2104 | | |
2105 | 0 | static LatValue native_load_bytecode(LatValue *args, int ac) { |
2106 | 0 | if (ac != 1 || args[0].type != VAL_STR) return value_nil(); |
2107 | 0 | const char *path = args[0].as.str_val; |
2108 | |
|
2109 | 0 | char *err = NULL; |
2110 | 0 | Chunk *chunk = chunk_load(path, &err); |
2111 | 0 | if (!chunk) { |
2112 | 0 | free(err); |
2113 | 0 | return value_nil(); |
2114 | 0 | } |
2115 | | |
2116 | 0 | if (!current_rt->active_vm) { |
2117 | 0 | chunk_free(chunk); |
2118 | 0 | return value_nil(); |
2119 | 0 | } |
2120 | | |
2121 | | /* Stack-VM bytecode (.latc) can only run on the stack VM */ |
2122 | 0 | StackVM *vm = (StackVM *)current_rt->active_vm; |
2123 | 0 | LatValue result; |
2124 | 0 | StackVMResult res = stackvm_run(vm, chunk, &result); |
2125 | 0 | chunk_free(chunk); |
2126 | |
|
2127 | 0 | if (res != STACKVM_OK) { |
2128 | 0 | free(vm->error); |
2129 | 0 | vm->error = NULL; |
2130 | 0 | return value_nil(); |
2131 | 0 | } |
2132 | 0 | return result; |
2133 | 0 | } |
2134 | | |
2135 | | /* ── Built-in module helpers ── */ |
2136 | | |
2137 | 546 | static void mod_set_native(LatValue *mod, const char *name, VMNativeFn fn) { |
2138 | 546 | LatValue v; |
2139 | 546 | v.type = VAL_CLOSURE; |
2140 | 546 | v.phase = VTAG_UNPHASED; |
2141 | 546 | v.as.closure.param_names = NULL; |
2142 | 546 | v.as.closure.param_count = 0; |
2143 | 546 | v.as.closure.body = NULL; |
2144 | 546 | v.as.closure.captured_env = NULL; |
2145 | 546 | v.as.closure.default_values = VM_NATIVE_MARKER; |
2146 | 546 | v.as.closure.has_variadic = false; |
2147 | 546 | v.as.closure.native_fn = (void *)fn; |
2148 | 546 | v.region_id = REGION_NONE; |
2149 | 546 | lat_map_set(mod->as.map.map, name, &v); |
2150 | 546 | } |
2151 | | |
2152 | 12 | static LatValue build_math_module(void) { |
2153 | 12 | LatValue m = value_map_new(); |
2154 | | /* Trig */ |
2155 | 12 | mod_set_native(&m, "sin", native_sin); |
2156 | 12 | mod_set_native(&m, "cos", native_cos); |
2157 | 12 | mod_set_native(&m, "tan", native_tan); |
2158 | 12 | mod_set_native(&m, "asin", native_asin); |
2159 | 12 | mod_set_native(&m, "acos", native_acos); |
2160 | 12 | mod_set_native(&m, "atan", native_atan); |
2161 | 12 | mod_set_native(&m, "atan2", native_atan2); |
2162 | 12 | mod_set_native(&m, "sinh", native_sinh); |
2163 | 12 | mod_set_native(&m, "cosh", native_cosh); |
2164 | 12 | mod_set_native(&m, "tanh", native_tanh); |
2165 | | /* Core */ |
2166 | 12 | mod_set_native(&m, "sqrt", native_sqrt); |
2167 | 12 | mod_set_native(&m, "pow", native_pow); |
2168 | 12 | mod_set_native(&m, "abs", native_abs); |
2169 | 12 | mod_set_native(&m, "floor", native_floor); |
2170 | 12 | mod_set_native(&m, "ceil", native_ceil); |
2171 | 12 | mod_set_native(&m, "round", native_round); |
2172 | 12 | mod_set_native(&m, "log", native_log); |
2173 | 12 | mod_set_native(&m, "log2", native_log2); |
2174 | 12 | mod_set_native(&m, "log10", native_log10); |
2175 | 12 | mod_set_native(&m, "exp", native_exp); |
2176 | 12 | mod_set_native(&m, "min", native_min); |
2177 | 12 | mod_set_native(&m, "max", native_max); |
2178 | 12 | mod_set_native(&m, "clamp", native_clamp); |
2179 | 12 | mod_set_native(&m, "sign", native_sign); |
2180 | 12 | mod_set_native(&m, "gcd", native_gcd); |
2181 | 12 | mod_set_native(&m, "lcm", native_lcm); |
2182 | 12 | mod_set_native(&m, "lerp", native_lerp); |
2183 | 12 | mod_set_native(&m, "is_nan", native_is_nan); |
2184 | 12 | mod_set_native(&m, "is_inf", native_is_inf); |
2185 | 12 | mod_set_native(&m, "random", native_random); |
2186 | 12 | mod_set_native(&m, "random_int", native_random_int); |
2187 | | /* Constants (as zero-arg closures) */ |
2188 | 12 | mod_set_native(&m, "PI", native_math_pi); |
2189 | 12 | mod_set_native(&m, "E", native_math_e); |
2190 | 12 | return m; |
2191 | 12 | } |
2192 | | |
2193 | 3 | static LatValue build_fs_module(void) { |
2194 | 3 | LatValue m = value_map_new(); |
2195 | 3 | mod_set_native(&m, "read_file", native_read_file); |
2196 | 3 | mod_set_native(&m, "write_file", native_write_file); |
2197 | 3 | mod_set_native(&m, "read_file_bytes", native_read_file_bytes); |
2198 | 3 | mod_set_native(&m, "write_file_bytes", native_write_file_bytes); |
2199 | 3 | mod_set_native(&m, "append_file", native_append_file); |
2200 | 3 | mod_set_native(&m, "copy_file", native_copy_file); |
2201 | 3 | mod_set_native(&m, "file_exists", native_file_exists); |
2202 | 3 | mod_set_native(&m, "is_file", native_is_file); |
2203 | 3 | mod_set_native(&m, "is_dir", native_is_dir); |
2204 | 3 | mod_set_native(&m, "file_size", native_file_size); |
2205 | 3 | mod_set_native(&m, "delete_file", native_delete_file); |
2206 | 3 | mod_set_native(&m, "mkdir", native_mkdir); |
2207 | 3 | mod_set_native(&m, "rmdir", native_rmdir); |
2208 | 3 | mod_set_native(&m, "rename", native_fs_rename); |
2209 | 3 | mod_set_native(&m, "chmod", native_chmod); |
2210 | 3 | mod_set_native(&m, "list_dir", native_list_dir); |
2211 | 3 | mod_set_native(&m, "glob", native_glob); |
2212 | 3 | mod_set_native(&m, "stat", native_stat); |
2213 | 3 | mod_set_native(&m, "realpath", native_realpath); |
2214 | 3 | mod_set_native(&m, "tempdir", native_tempdir); |
2215 | 3 | mod_set_native(&m, "tempfile", native_tempfile); |
2216 | 3 | return m; |
2217 | 3 | } |
2218 | | |
2219 | 3 | static LatValue build_path_module(void) { |
2220 | 3 | LatValue m = value_map_new(); |
2221 | 3 | mod_set_native(&m, "join", native_path_join); |
2222 | 3 | mod_set_native(&m, "dir", native_path_dir); |
2223 | 3 | mod_set_native(&m, "base", native_path_base); |
2224 | 3 | mod_set_native(&m, "ext", native_path_ext); |
2225 | 3 | return m; |
2226 | 3 | } |
2227 | | |
2228 | 3 | static LatValue build_json_module(void) { |
2229 | 3 | LatValue m = value_map_new(); |
2230 | 3 | mod_set_native(&m, "parse", native_json_parse); |
2231 | 3 | mod_set_native(&m, "stringify", native_json_stringify); |
2232 | 3 | return m; |
2233 | 3 | } |
2234 | | |
2235 | 0 | static LatValue build_toml_module(void) { |
2236 | 0 | LatValue m = value_map_new(); |
2237 | 0 | mod_set_native(&m, "parse", native_toml_parse); |
2238 | 0 | mod_set_native(&m, "stringify", native_toml_stringify); |
2239 | 0 | return m; |
2240 | 0 | } |
2241 | | |
2242 | 0 | static LatValue build_yaml_module(void) { |
2243 | 0 | LatValue m = value_map_new(); |
2244 | 0 | mod_set_native(&m, "parse", native_yaml_parse); |
2245 | 0 | mod_set_native(&m, "stringify", native_yaml_stringify); |
2246 | 0 | return m; |
2247 | 0 | } |
2248 | | |
2249 | 3 | static LatValue build_crypto_module(void) { |
2250 | 3 | LatValue m = value_map_new(); |
2251 | 3 | mod_set_native(&m, "sha256", native_sha256); |
2252 | 3 | mod_set_native(&m, "md5", native_md5); |
2253 | 3 | mod_set_native(&m, "base64_encode", native_base64_encode); |
2254 | 3 | mod_set_native(&m, "base64_decode", native_base64_decode); |
2255 | 3 | mod_set_native(&m, "url_encode", native_url_encode); |
2256 | 3 | mod_set_native(&m, "url_decode", native_url_decode); |
2257 | 3 | return m; |
2258 | 3 | } |
2259 | | |
2260 | 0 | static LatValue build_http_module(void) { |
2261 | 0 | LatValue m = value_map_new(); |
2262 | 0 | mod_set_native(&m, "get", native_http_get); |
2263 | 0 | mod_set_native(&m, "post", native_http_post); |
2264 | 0 | mod_set_native(&m, "request", native_http_request); |
2265 | 0 | return m; |
2266 | 0 | } |
2267 | | |
2268 | 0 | static LatValue build_net_module(void) { |
2269 | 0 | LatValue m = value_map_new(); |
2270 | | /* TCP */ |
2271 | 0 | mod_set_native(&m, "tcp_listen", native_tcp_listen); |
2272 | 0 | mod_set_native(&m, "tcp_accept", native_tcp_accept); |
2273 | 0 | mod_set_native(&m, "tcp_connect", native_tcp_connect); |
2274 | 0 | mod_set_native(&m, "tcp_read", native_tcp_read); |
2275 | 0 | mod_set_native(&m, "tcp_read_bytes", native_tcp_read_bytes); |
2276 | 0 | mod_set_native(&m, "tcp_write", native_tcp_write); |
2277 | 0 | mod_set_native(&m, "tcp_close", native_tcp_close); |
2278 | 0 | mod_set_native(&m, "tcp_peer_addr", native_tcp_peer_addr); |
2279 | 0 | mod_set_native(&m, "tcp_set_timeout", native_tcp_set_timeout); |
2280 | | /* TLS */ |
2281 | 0 | mod_set_native(&m, "tls_connect", native_tls_connect); |
2282 | 0 | mod_set_native(&m, "tls_read", native_tls_read); |
2283 | 0 | mod_set_native(&m, "tls_read_bytes", native_tls_read_bytes); |
2284 | 0 | mod_set_native(&m, "tls_write", native_tls_write); |
2285 | 0 | mod_set_native(&m, "tls_close", native_tls_close); |
2286 | 0 | mod_set_native(&m, "tls_available", native_tls_available); |
2287 | 0 | return m; |
2288 | 0 | } |
2289 | | |
2290 | 3 | static LatValue build_os_module(void) { |
2291 | 3 | LatValue m = value_map_new(); |
2292 | 3 | mod_set_native(&m, "exec", native_exec_cmd); |
2293 | 3 | mod_set_native(&m, "shell", native_shell); |
2294 | 3 | mod_set_native(&m, "env", native_env); |
2295 | 3 | mod_set_native(&m, "env_set", native_env_set); |
2296 | 3 | mod_set_native(&m, "env_keys", native_env_keys); |
2297 | 3 | mod_set_native(&m, "cwd", native_cwd); |
2298 | 3 | mod_set_native(&m, "platform", native_platform); |
2299 | 3 | mod_set_native(&m, "hostname", native_hostname); |
2300 | 3 | mod_set_native(&m, "pid", native_pid); |
2301 | 3 | mod_set_native(&m, "args", native_args); |
2302 | 3 | return m; |
2303 | 3 | } |
2304 | | |
2305 | 3 | static LatValue build_time_module(void) { |
2306 | 3 | LatValue m = value_map_new(); |
2307 | 3 | mod_set_native(&m, "now", native_time); |
2308 | 3 | mod_set_native(&m, "sleep", native_sleep); |
2309 | 3 | mod_set_native(&m, "format", native_time_format); |
2310 | 3 | mod_set_native(&m, "parse", native_time_parse); |
2311 | 3 | return m; |
2312 | 3 | } |
2313 | | |
2314 | 3 | static LatValue build_regex_module(void) { |
2315 | 3 | LatValue m = value_map_new(); |
2316 | 3 | mod_set_native(&m, "match", native_regex_match); |
2317 | 3 | mod_set_native(&m, "find_all", native_regex_find_all); |
2318 | 3 | mod_set_native(&m, "replace", native_regex_replace); |
2319 | 3 | return m; |
2320 | 3 | } |
2321 | | |
2322 | 282 | bool rt_try_builtin_import(const char *name, LatValue *out) { |
2323 | | /* Paths with directory separators or leading dot are never built-in */ |
2324 | 282 | if (strchr(name, '/') || strchr(name, '\\') || name[0] == '.') return false; |
2325 | | |
2326 | 36 | if (strcmp(name, "math") == 0) { *out = build_math_module(); return true; } |
2327 | 24 | if (strcmp(name, "fs") == 0) { *out = build_fs_module(); return true; } |
2328 | 21 | if (strcmp(name, "path") == 0) { *out = build_path_module(); return true; } |
2329 | 18 | if (strcmp(name, "json") == 0) { *out = build_json_module(); return true; } |
2330 | 15 | if (strcmp(name, "toml") == 0) { *out = build_toml_module(); return true; } |
2331 | 15 | if (strcmp(name, "yaml") == 0) { *out = build_yaml_module(); return true; } |
2332 | 15 | if (strcmp(name, "crypto") == 0) { *out = build_crypto_module(); return true; } |
2333 | 12 | if (strcmp(name, "http") == 0) { *out = build_http_module(); return true; } |
2334 | 12 | if (strcmp(name, "net") == 0) { *out = build_net_module(); return true; } |
2335 | 12 | if (strcmp(name, "os") == 0) { *out = build_os_module(); return true; } |
2336 | 9 | if (strcmp(name, "time") == 0) { *out = build_time_module(); return true; } |
2337 | 6 | if (strcmp(name, "regex") == 0) { *out = build_regex_module(); return true; } |
2338 | 3 | return false; |
2339 | 6 | } |
2340 | | |
2341 | | /* ── Registration helper ── */ |
2342 | | |
2343 | 276k | static void rt_register_native(LatRuntime *rt, const char *name, VMNativeFn fn, int arity) { |
2344 | 276k | (void)arity; |
2345 | 276k | LatValue v; |
2346 | 276k | v.type = VAL_CLOSURE; |
2347 | 276k | v.phase = VTAG_UNPHASED; |
2348 | 276k | v.as.closure.param_names = NULL; |
2349 | 276k | v.as.closure.param_count = 0; |
2350 | 276k | v.as.closure.body = NULL; |
2351 | 276k | v.as.closure.captured_env = NULL; |
2352 | 276k | v.as.closure.default_values = VM_NATIVE_MARKER; |
2353 | 276k | v.as.closure.has_variadic = false; |
2354 | 276k | v.as.closure.native_fn = (void *)fn; |
2355 | 276k | v.region_id = REGION_NONE; |
2356 | 276k | env_define(rt->env, name, v); |
2357 | 276k | } |
2358 | | |
2359 | | /* ── Runtime lifecycle ── */ |
2360 | | |
2361 | 1.70k | void lat_runtime_init(LatRuntime *rt) { |
2362 | 1.70k | memset(rt, 0, sizeof(LatRuntime)); |
2363 | 1.70k | rt->env = env_new(); |
2364 | 1.70k | rt->struct_meta = NULL; |
2365 | 1.70k | rt->error = NULL; |
2366 | 1.70k | rt->tracked_vars = NULL; |
2367 | 1.70k | rt->tracked_count = 0; |
2368 | 1.70k | rt->tracked_cap = 0; |
2369 | 1.70k | rt->tracking_active = false; |
2370 | 1.70k | rt->pressures = NULL; |
2371 | 1.70k | rt->pressure_count = 0; |
2372 | 1.70k | rt->pressure_cap = 0; |
2373 | 1.70k | rt->reactions = NULL; |
2374 | 1.70k | rt->reaction_count = 0; |
2375 | 1.70k | rt->reaction_cap = 0; |
2376 | 1.70k | rt->bonds = NULL; |
2377 | 1.70k | rt->bond_count = 0; |
2378 | 1.70k | rt->bond_cap = 0; |
2379 | 1.70k | rt->seeds = NULL; |
2380 | 1.70k | rt->seed_count = 0; |
2381 | 1.70k | rt->seed_cap = 0; |
2382 | 1.70k | rt->module_cache = lat_map_new(sizeof(LatValue)); |
2383 | 1.70k | rt->required_files = lat_map_new(sizeof(bool)); |
2384 | 1.70k | rt->loaded_extensions = lat_map_new(sizeof(LatValue)); |
2385 | 1.70k | rt->script_dir = NULL; |
2386 | 1.70k | rt->prog_argc = 0; |
2387 | 1.70k | rt->prog_argv = NULL; |
2388 | 1.70k | rt->active_vm = NULL; |
2389 | 1.70k | rt->call_closure = NULL; |
2390 | 1.70k | rt->find_local_value = NULL; |
2391 | 1.70k | rt->current_line = NULL; |
2392 | 1.70k | rt->get_var_by_name = NULL; |
2393 | 1.70k | rt->set_var_by_name = NULL; |
2394 | | |
2395 | | /* Register builtin functions */ |
2396 | 1.70k | rt_register_native(rt, "to_string", native_to_string, 1); |
2397 | 1.70k | rt_register_native(rt, "typeof", native_typeof, 1); |
2398 | 1.70k | rt_register_native(rt, "len", native_len, 1); |
2399 | 1.70k | rt_register_native(rt, "parse_int", native_parse_int, 1); |
2400 | 1.70k | rt_register_native(rt, "parse_float", native_parse_float, 1); |
2401 | 1.70k | rt_register_native(rt, "ord", native_ord, 1); |
2402 | 1.70k | rt_register_native(rt, "chr", native_chr, 1); |
2403 | 1.70k | rt_register_native(rt, "abs", native_abs, 1); |
2404 | 1.70k | rt_register_native(rt, "floor", native_floor, 1); |
2405 | 1.70k | rt_register_native(rt, "ceil", native_ceil, 1); |
2406 | 1.70k | rt_register_native(rt, "exit", native_exit, 1); |
2407 | 1.70k | rt_register_native(rt, "error", native_error, 1); |
2408 | 1.70k | rt_register_native(rt, "is_error", native_is_error, 1); |
2409 | 1.70k | rt_register_native(rt, "Map::new", native_map_new, 0); |
2410 | 1.70k | rt_register_native(rt, "Set::new", native_set_new, 0); |
2411 | 1.70k | rt_register_native(rt, "Set::from", native_set_from, 1); |
2412 | 1.70k | rt_register_native(rt, "Channel::new", native_channel_new, 0); |
2413 | 1.70k | rt_register_native(rt, "Buffer::new", native_buffer_new, 1); |
2414 | 1.70k | rt_register_native(rt, "Buffer::from", native_buffer_from, 1); |
2415 | 1.70k | rt_register_native(rt, "Buffer::from_string", native_buffer_from_string, 1); |
2416 | 1.70k | rt_register_native(rt, "Ref::new", native_ref_new, 1); |
2417 | 1.70k | rt_register_native(rt, "phase_of", native_phase_of, 1); |
2418 | 1.70k | rt_register_native(rt, "assert", native_assert, 2); |
2419 | 1.70k | rt_register_native(rt, "version", native_version, 0); |
2420 | 1.70k | rt_register_native(rt, "input", native_input, 1); |
2421 | | |
2422 | | /* Phase system */ |
2423 | 1.70k | rt_register_native(rt, "track", native_track, 1); |
2424 | 1.70k | rt_register_native(rt, "phases", native_phases, 1); |
2425 | 1.70k | rt_register_native(rt, "history", native_history, 1); |
2426 | 1.70k | rt_register_native(rt, "rewind", native_rewind, 2); |
2427 | 1.70k | rt_register_native(rt, "pressurize", native_pressurize, 2); |
2428 | 1.70k | rt_register_native(rt, "depressurize", native_depressurize, 1); |
2429 | 1.70k | rt_register_native(rt, "pressure_of", native_pressure_of, 1); |
2430 | 1.70k | rt_register_native(rt, "grow", native_grow, 1); |
2431 | | |
2432 | | /* Math */ |
2433 | 1.70k | rt_register_native(rt, "round", native_round, 1); |
2434 | 1.70k | rt_register_native(rt, "sqrt", native_sqrt, 1); |
2435 | 1.70k | rt_register_native(rt, "pow", native_pow, 2); |
2436 | 1.70k | rt_register_native(rt, "min", native_min, 2); |
2437 | 1.70k | rt_register_native(rt, "max", native_max, 2); |
2438 | 1.70k | rt_register_native(rt, "random", native_random, 0); |
2439 | 1.70k | rt_register_native(rt, "random_int", native_random_int, 2); |
2440 | 1.70k | rt_register_native(rt, "log", native_log, 1); |
2441 | 1.70k | rt_register_native(rt, "log2", native_log2, 1); |
2442 | 1.70k | rt_register_native(rt, "log10", native_log10, 1); |
2443 | 1.70k | rt_register_native(rt, "sin", native_sin, 1); |
2444 | 1.70k | rt_register_native(rt, "cos", native_cos, 1); |
2445 | 1.70k | rt_register_native(rt, "tan", native_tan, 1); |
2446 | 1.70k | rt_register_native(rt, "asin", native_asin, 1); |
2447 | 1.70k | rt_register_native(rt, "acos", native_acos, 1); |
2448 | 1.70k | rt_register_native(rt, "atan", native_atan, 1); |
2449 | 1.70k | rt_register_native(rt, "atan2", native_atan2, 2); |
2450 | 1.70k | rt_register_native(rt, "exp", native_exp, 1); |
2451 | 1.70k | rt_register_native(rt, "sign", native_sign, 1); |
2452 | 1.70k | rt_register_native(rt, "gcd", native_gcd, 2); |
2453 | 1.70k | rt_register_native(rt, "lcm", native_lcm, 2); |
2454 | 1.70k | rt_register_native(rt, "is_nan", native_is_nan, 1); |
2455 | 1.70k | rt_register_native(rt, "is_inf", native_is_inf, 1); |
2456 | 1.70k | rt_register_native(rt, "sinh", native_sinh, 1); |
2457 | 1.70k | rt_register_native(rt, "cosh", native_cosh, 1); |
2458 | 1.70k | rt_register_native(rt, "tanh", native_tanh, 1); |
2459 | 1.70k | rt_register_native(rt, "lerp", native_lerp, 3); |
2460 | 1.70k | rt_register_native(rt, "clamp", native_clamp, 3); |
2461 | 1.70k | rt_register_native(rt, "math_pi", native_math_pi, 0); |
2462 | 1.70k | rt_register_native(rt, "math_e", native_math_e, 0); |
2463 | | |
2464 | | /* File system */ |
2465 | 1.70k | rt_register_native(rt, "read_file", native_read_file, 1); |
2466 | 1.70k | rt_register_native(rt, "write_file", native_write_file, 2); |
2467 | 1.70k | rt_register_native(rt, "read_file_bytes", native_read_file_bytes, 1); |
2468 | 1.70k | rt_register_native(rt, "write_file_bytes", native_write_file_bytes, 2); |
2469 | 1.70k | rt_register_native(rt, "file_exists", native_file_exists, 1); |
2470 | 1.70k | rt_register_native(rt, "delete_file", native_delete_file, 1); |
2471 | 1.70k | rt_register_native(rt, "list_dir", native_list_dir, 1); |
2472 | 1.70k | rt_register_native(rt, "append_file", native_append_file, 2); |
2473 | 1.70k | rt_register_native(rt, "mkdir", native_mkdir, 1); |
2474 | 1.70k | rt_register_native(rt, "rename", native_fs_rename, 2); |
2475 | 1.70k | rt_register_native(rt, "is_dir", native_is_dir, 1); |
2476 | 1.70k | rt_register_native(rt, "is_file", native_is_file, 1); |
2477 | 1.70k | rt_register_native(rt, "rmdir", native_rmdir, 1); |
2478 | 1.70k | rt_register_native(rt, "glob", native_glob, 1); |
2479 | 1.70k | rt_register_native(rt, "stat", native_stat, 1); |
2480 | 1.70k | rt_register_native(rt, "copy_file", native_copy_file, 2); |
2481 | 1.70k | rt_register_native(rt, "realpath", native_realpath, 1); |
2482 | 1.70k | rt_register_native(rt, "tempdir", native_tempdir, 0); |
2483 | 1.70k | rt_register_native(rt, "tempfile", native_tempfile, 0); |
2484 | 1.70k | rt_register_native(rt, "chmod", native_chmod, 2); |
2485 | 1.70k | rt_register_native(rt, "file_size", native_file_size, 1); |
2486 | | |
2487 | | /* Bytecode compilation/loading */ |
2488 | 1.70k | rt_register_native(rt, "compile_file", native_compile_file, 1); |
2489 | 1.70k | rt_register_native(rt, "load_bytecode", native_load_bytecode, 1); |
2490 | | |
2491 | | /* Path */ |
2492 | 1.70k | rt_register_native(rt, "path_join", native_path_join, -1); |
2493 | 1.70k | rt_register_native(rt, "path_dir", native_path_dir, 1); |
2494 | 1.70k | rt_register_native(rt, "path_base", native_path_base, 1); |
2495 | 1.70k | rt_register_native(rt, "path_ext", native_path_ext, 1); |
2496 | | |
2497 | | /* Network TCP */ |
2498 | 1.70k | rt_register_native(rt, "tcp_listen", native_tcp_listen, 2); |
2499 | 1.70k | rt_register_native(rt, "tcp_accept", native_tcp_accept, 1); |
2500 | 1.70k | rt_register_native(rt, "tcp_connect", native_tcp_connect, 2); |
2501 | 1.70k | rt_register_native(rt, "tcp_read", native_tcp_read, 1); |
2502 | 1.70k | rt_register_native(rt, "tcp_read_bytes", native_tcp_read_bytes, 2); |
2503 | 1.70k | rt_register_native(rt, "tcp_write", native_tcp_write, 2); |
2504 | 1.70k | rt_register_native(rt, "tcp_close", native_tcp_close, 1); |
2505 | 1.70k | rt_register_native(rt, "tcp_peer_addr", native_tcp_peer_addr, 1); |
2506 | 1.70k | rt_register_native(rt, "tcp_set_timeout", native_tcp_set_timeout, 2); |
2507 | | |
2508 | | /* TLS */ |
2509 | 1.70k | rt_register_native(rt, "tls_connect", native_tls_connect, 2); |
2510 | 1.70k | rt_register_native(rt, "tls_read", native_tls_read, 1); |
2511 | 1.70k | rt_register_native(rt, "tls_read_bytes", native_tls_read_bytes, 2); |
2512 | 1.70k | rt_register_native(rt, "tls_write", native_tls_write, 2); |
2513 | 1.70k | rt_register_native(rt, "tls_close", native_tls_close, 1); |
2514 | 1.70k | rt_register_native(rt, "tls_available", native_tls_available, 0); |
2515 | | |
2516 | | /* HTTP */ |
2517 | 1.70k | rt_register_native(rt, "http_get", native_http_get, 1); |
2518 | 1.70k | rt_register_native(rt, "http_post", native_http_post, 2); |
2519 | 1.70k | rt_register_native(rt, "http_request", native_http_request, 3); |
2520 | | |
2521 | | /* JSON/TOML/YAML */ |
2522 | 1.70k | rt_register_native(rt, "json_parse", native_json_parse, 1); |
2523 | 1.70k | rt_register_native(rt, "json_stringify", native_json_stringify, 1); |
2524 | 1.70k | rt_register_native(rt, "toml_parse", native_toml_parse, 1); |
2525 | 1.70k | rt_register_native(rt, "toml_stringify", native_toml_stringify, 1); |
2526 | 1.70k | rt_register_native(rt, "yaml_parse", native_yaml_parse, 1); |
2527 | 1.70k | rt_register_native(rt, "yaml_stringify", native_yaml_stringify, 1); |
2528 | | |
2529 | | /* Crypto */ |
2530 | 1.70k | rt_register_native(rt, "sha256", native_sha256, 1); |
2531 | 1.70k | rt_register_native(rt, "md5", native_md5, 1); |
2532 | 1.70k | rt_register_native(rt, "base64_encode", native_base64_encode, 1); |
2533 | 1.70k | rt_register_native(rt, "base64_decode", native_base64_decode, 1); |
2534 | | |
2535 | | /* Regex */ |
2536 | 1.70k | rt_register_native(rt, "regex_match", native_regex_match, 2); |
2537 | 1.70k | rt_register_native(rt, "regex_find_all", native_regex_find_all, 2); |
2538 | 1.70k | rt_register_native(rt, "regex_replace", native_regex_replace, 3); |
2539 | | |
2540 | | /* Time/DateTime */ |
2541 | 1.70k | rt_register_native(rt, "time", native_time, 0); |
2542 | 1.70k | rt_register_native(rt, "sleep", native_sleep, 1); |
2543 | 1.70k | rt_register_native(rt, "time_format", native_time_format, 2); |
2544 | 1.70k | rt_register_native(rt, "time_parse", native_time_parse, 2); |
2545 | | |
2546 | | /* Environment */ |
2547 | 1.70k | rt_register_native(rt, "env", native_env, 1); |
2548 | 1.70k | rt_register_native(rt, "env_set", native_env_set, 2); |
2549 | 1.70k | rt_register_native(rt, "env_keys", native_env_keys, 0); |
2550 | | |
2551 | | /* Process */ |
2552 | 1.70k | rt_register_native(rt, "cwd", native_cwd, 0); |
2553 | 1.70k | rt_register_native(rt, "exec", native_exec_cmd, 1); |
2554 | 1.70k | rt_register_native(rt, "shell", native_shell, 1); |
2555 | 1.70k | rt_register_native(rt, "platform", native_platform, 0); |
2556 | 1.70k | rt_register_native(rt, "hostname", native_hostname, 0); |
2557 | 1.70k | rt_register_native(rt, "pid", native_pid, 0); |
2558 | | |
2559 | | /* Type/utility */ |
2560 | 1.70k | rt_register_native(rt, "to_int", native_to_int, 1); |
2561 | 1.70k | rt_register_native(rt, "to_float", native_to_float, 1); |
2562 | 1.70k | rt_register_native(rt, "struct_name", native_struct_name, 1); |
2563 | 1.70k | rt_register_native(rt, "struct_fields", native_struct_fields, 1); |
2564 | 1.70k | rt_register_native(rt, "struct_to_map", native_struct_to_map, 1); |
2565 | 1.70k | rt_register_native(rt, "repr", native_repr, 1); |
2566 | 1.70k | rt_register_native(rt, "format", native_format, -1); |
2567 | 1.70k | rt_register_native(rt, "range", native_range, -1); |
2568 | 1.70k | rt_register_native(rt, "print_raw", native_print_raw, -1); |
2569 | 1.70k | rt_register_native(rt, "eprint", native_eprint, -1); |
2570 | 1.70k | rt_register_native(rt, "identity", native_identity, 1); |
2571 | 1.70k | rt_register_native(rt, "debug_assert", native_debug_assert, 2); |
2572 | 1.70k | rt_register_native(rt, "panic", native_panic, 1); |
2573 | | |
2574 | | /* Module loading */ |
2575 | 1.70k | rt_register_native(rt, "require", native_require, 1); |
2576 | 1.70k | rt_register_native(rt, "require_ext", native_require_ext, 1); |
2577 | | |
2578 | | /* Metaprogramming/reflection */ |
2579 | 1.70k | rt_register_native(rt, "args", native_args, 0); |
2580 | 1.70k | rt_register_native(rt, "struct_from_map", native_struct_from_map, 2); |
2581 | 1.70k | rt_register_native(rt, "is_complete", native_is_complete, 1); |
2582 | 1.70k | rt_register_native(rt, "tokenize", native_tokenize, 1); |
2583 | 1.70k | rt_register_native(rt, "lat_eval", native_lat_eval, 1); |
2584 | | |
2585 | | /* Bitwise float conversion (for bytecode serialization) */ |
2586 | 1.70k | rt_register_native(rt, "float_to_bits", native_float_to_bits, 1); |
2587 | 1.70k | rt_register_native(rt, "bits_to_float", native_bits_to_float, 1); |
2588 | | |
2589 | | /* URL encoding */ |
2590 | 1.70k | rt_register_native(rt, "url_encode", native_url_encode, 1); |
2591 | 1.70k | rt_register_native(rt, "url_decode", native_url_decode, 1); |
2592 | | |
2593 | | /* CSV */ |
2594 | 1.70k | rt_register_native(rt, "csv_parse", native_csv_parse, 1); |
2595 | 1.70k | rt_register_native(rt, "csv_stringify", native_csv_stringify, 1); |
2596 | | |
2597 | | /* Functional */ |
2598 | 1.70k | rt_register_native(rt, "pipe", native_pipe, -1); |
2599 | 1.70k | rt_register_native(rt, "compose", native_compose, 2); |
2600 | | |
2601 | 1.70k | intern_init(); |
2602 | | |
2603 | 1.70k | intern_init(); |
2604 | 1.70k | } |
2605 | | |
2606 | 1.70k | void lat_runtime_free(LatRuntime *rt) { |
2607 | 1.70k | if (rt->env) env_free(rt->env); |
2608 | 1.70k | if (rt->struct_meta) env_free(rt->struct_meta); |
2609 | 1.70k | free(rt->error); |
2610 | | |
2611 | | /* Free module cache */ |
2612 | 29.0k | for (size_t i = 0; i < rt->module_cache.cap; i++) { |
2613 | 27.2k | if (rt->module_cache.entries[i].state == MAP_OCCUPIED) { |
2614 | 0 | LatValue *v = (LatValue *)rt->module_cache.entries[i].value; |
2615 | 0 | value_free(v); |
2616 | 0 | } |
2617 | 27.2k | } |
2618 | 1.70k | lat_map_free(&rt->module_cache); |
2619 | 1.70k | lat_map_free(&rt->required_files); |
2620 | | |
2621 | | /* Free extension cache */ |
2622 | 29.0k | for (size_t i = 0; i < rt->loaded_extensions.cap; i++) { |
2623 | 27.2k | if (rt->loaded_extensions.entries[i].state == MAP_OCCUPIED) { |
2624 | 30 | LatValue *v = (LatValue *)rt->loaded_extensions.entries[i].value; |
2625 | 30 | value_free(v); |
2626 | 30 | } |
2627 | 27.2k | } |
2628 | 1.70k | lat_map_free(&rt->loaded_extensions); |
2629 | 1.70k | free(rt->script_dir); |
2630 | | |
2631 | | /* Free tracked vars */ |
2632 | 1.73k | for (size_t i = 0; i < rt->tracked_count; i++) { |
2633 | 26 | free(rt->tracked_vars[i].name); |
2634 | 86 | for (size_t j = 0; j < rt->tracked_vars[i].snap_count; j++) { |
2635 | 60 | free(rt->tracked_vars[i].snapshots[j].phase); |
2636 | 60 | free(rt->tracked_vars[i].snapshots[j].fn_name); |
2637 | 60 | value_free(&rt->tracked_vars[i].snapshots[j].value); |
2638 | 60 | } |
2639 | 26 | free(rt->tracked_vars[i].snapshots); |
2640 | 26 | } |
2641 | 1.70k | free(rt->tracked_vars); |
2642 | | |
2643 | | /* Free pressures */ |
2644 | 1.71k | for (size_t i = 0; i < rt->pressure_count; i++) { |
2645 | 10 | free(rt->pressures[i].name); |
2646 | 10 | free(rt->pressures[i].mode); |
2647 | 10 | } |
2648 | 1.70k | free(rt->pressures); |
2649 | | |
2650 | | /* Free reactions */ |
2651 | 1.72k | for (size_t i = 0; i < rt->reaction_count; i++) { |
2652 | 14 | free(rt->reactions[i].var_name); |
2653 | 30 | for (size_t j = 0; j < rt->reactions[i].cb_count; j++) |
2654 | 16 | value_free(&rt->reactions[i].callbacks[j]); |
2655 | 14 | free(rt->reactions[i].callbacks); |
2656 | 14 | } |
2657 | 1.70k | free(rt->reactions); |
2658 | | |
2659 | | /* Free bonds */ |
2660 | 1.70k | for (size_t i = 0; i < rt->bond_count; i++) { |
2661 | 2 | free(rt->bonds[i].target); |
2662 | 4 | for (size_t j = 0; j < rt->bonds[i].dep_count; j++) { |
2663 | 2 | free(rt->bonds[i].deps[j]); |
2664 | 2 | if (rt->bonds[i].dep_strategies) free(rt->bonds[i].dep_strategies[j]); |
2665 | 2 | } |
2666 | 2 | free(rt->bonds[i].deps); |
2667 | 2 | free(rt->bonds[i].dep_strategies); |
2668 | 2 | } |
2669 | 1.70k | free(rt->bonds); |
2670 | | |
2671 | | /* Free seeds */ |
2672 | 1.70k | for (size_t i = 0; i < rt->seed_count; i++) { |
2673 | 2 | free(rt->seeds[i].var_name); |
2674 | 2 | value_free(&rt->seeds[i].contract); |
2675 | 2 | } |
2676 | 1.70k | free(rt->seeds); |
2677 | | |
2678 | 1.70k | intern_free(); |
2679 | 1.70k | } |