/Users/alexjokela/projects/lattice/src/ext.c
Line | Count | Source |
1 | | #include "ext.h" |
2 | | #include "lattice_ext.h" |
3 | | #include "eval.h" |
4 | | #include "env.h" |
5 | | |
6 | | #include <stdio.h> |
7 | | #include <stdlib.h> |
8 | | #include <string.h> |
9 | | |
10 | | #ifndef __EMSCRIPTEN__ |
11 | | #include <dlfcn.h> |
12 | | #endif |
13 | | |
14 | | /* ── LatExtValue: thin wrapper around LatValue ── */ |
15 | | |
16 | | struct LatExtValue { |
17 | | LatValue val; |
18 | | }; |
19 | | |
20 | | /* ── LatExtContext: collects registered functions during init ── */ |
21 | | |
22 | | typedef struct { |
23 | | char **names; |
24 | | LatExtFn *fns; |
25 | | size_t count; |
26 | | size_t cap; |
27 | | } ExtRegistry; |
28 | | |
29 | | struct LatExtContext { |
30 | | ExtRegistry reg; |
31 | | }; |
32 | | |
33 | | /* ── Registration ── */ |
34 | | |
35 | 270 | void lat_ext_register(LatExtContext *ctx, const char *name, LatExtFn fn) { |
36 | 270 | if (ctx->reg.count >= ctx->reg.cap) { |
37 | 45 | ctx->reg.cap = ctx->reg.cap ? ctx->reg.cap * 2 : 8; |
38 | 45 | ctx->reg.names = realloc(ctx->reg.names, ctx->reg.cap * sizeof(char *)); |
39 | 45 | ctx->reg.fns = realloc(ctx->reg.fns, ctx->reg.cap * sizeof(LatExtFn)); |
40 | 45 | } |
41 | 270 | ctx->reg.names[ctx->reg.count] = strdup(name); |
42 | 270 | ctx->reg.fns[ctx->reg.count] = fn; |
43 | 270 | ctx->reg.count++; |
44 | 270 | } |
45 | | |
46 | | /* ── Constructors ── */ |
47 | | |
48 | 138 | LatExtValue *lat_ext_int(int64_t v) { |
49 | 138 | LatExtValue *ev = malloc(sizeof(LatExtValue)); |
50 | 138 | ev->val = value_int(v); |
51 | 138 | return ev; |
52 | 138 | } |
53 | | |
54 | 9 | LatExtValue *lat_ext_float(double v) { |
55 | 9 | LatExtValue *ev = malloc(sizeof(LatExtValue)); |
56 | 9 | ev->val = value_float(v); |
57 | 9 | return ev; |
58 | 9 | } |
59 | | |
60 | 0 | LatExtValue *lat_ext_bool(bool v) { |
61 | 0 | LatExtValue *ev = malloc(sizeof(LatExtValue)); |
62 | 0 | ev->val = value_bool(v); |
63 | 0 | return ev; |
64 | 0 | } |
65 | | |
66 | 27 | LatExtValue *lat_ext_string(const char *s) { |
67 | 27 | LatExtValue *ev = malloc(sizeof(LatExtValue)); |
68 | 27 | ev->val = value_string(s); |
69 | 27 | return ev; |
70 | 27 | } |
71 | | |
72 | 42 | LatExtValue *lat_ext_nil(void) { |
73 | 42 | LatExtValue *ev = malloc(sizeof(LatExtValue)); |
74 | 42 | ev->val = value_nil(); |
75 | 42 | return ev; |
76 | 42 | } |
77 | | |
78 | 21 | LatExtValue *lat_ext_array(LatExtValue **elems, size_t len) { |
79 | 21 | LatValue *vals = malloc(len * sizeof(LatValue)); |
80 | 42 | for (size_t i = 0; i < len; i++) { |
81 | 21 | vals[i] = value_deep_clone(&elems[i]->val); |
82 | 21 | } |
83 | 21 | LatExtValue *ev = malloc(sizeof(LatExtValue)); |
84 | 21 | ev->val = value_array(vals, len); |
85 | 21 | free(vals); |
86 | 21 | return ev; |
87 | 21 | } |
88 | | |
89 | 21 | LatExtValue *lat_ext_map_new(void) { |
90 | 21 | LatExtValue *ev = malloc(sizeof(LatExtValue)); |
91 | 21 | ev->val = value_map_new(); |
92 | 21 | return ev; |
93 | 21 | } |
94 | | |
95 | 54 | void lat_ext_map_set(LatExtValue *map, const char *key, LatExtValue *val) { |
96 | 54 | if (map->val.type != VAL_MAP) return; |
97 | 54 | LatValue v = value_deep_clone(&val->val); |
98 | 54 | lat_map_set(map->val.as.map.map, key, &v); |
99 | 54 | } |
100 | | |
101 | 9 | LatExtValue *lat_ext_error(const char *msg) { |
102 | | /* Errors are strings prefixed with "EVAL_ERROR:" */ |
103 | 9 | char *err = NULL; |
104 | 9 | (void)asprintf(&err, "EVAL_ERROR:%s", msg); |
105 | 9 | LatExtValue *ev = malloc(sizeof(LatExtValue)); |
106 | 9 | ev->val = value_string_owned(err); |
107 | 9 | return ev; |
108 | 9 | } |
109 | | |
110 | | /* ── Type query ── */ |
111 | | |
112 | 333 | LatExtType lat_ext_type(const LatExtValue *v) { |
113 | 333 | switch (v->val.type) { |
114 | 156 | case VAL_INT: return LAT_EXT_INT; |
115 | 3 | case VAL_FLOAT: return LAT_EXT_FLOAT; |
116 | 0 | case VAL_BOOL: return LAT_EXT_BOOL; |
117 | 156 | case VAL_STR: return LAT_EXT_STRING; |
118 | 15 | case VAL_ARRAY: return LAT_EXT_ARRAY; |
119 | 0 | case VAL_MAP: return LAT_EXT_MAP; |
120 | 3 | case VAL_NIL: return LAT_EXT_NIL; |
121 | 0 | default: return LAT_EXT_OTHER; |
122 | 333 | } |
123 | 333 | } |
124 | | |
125 | | /* ── Accessors ── */ |
126 | | |
127 | 156 | int64_t lat_ext_as_int(const LatExtValue *v) { |
128 | 156 | return v->val.as.int_val; |
129 | 156 | } |
130 | | |
131 | 3 | double lat_ext_as_float(const LatExtValue *v) { |
132 | 3 | return v->val.as.float_val; |
133 | 3 | } |
134 | | |
135 | 0 | bool lat_ext_as_bool(const LatExtValue *v) { |
136 | 0 | return v->val.as.bool_val; |
137 | 0 | } |
138 | | |
139 | 156 | const char *lat_ext_as_string(const LatExtValue *v) { |
140 | 156 | return v->val.as.str_val; |
141 | 156 | } |
142 | | |
143 | 15 | size_t lat_ext_array_len(const LatExtValue *v) { |
144 | 15 | if (v->val.type != VAL_ARRAY) return 0; |
145 | 15 | return v->val.as.array.len; |
146 | 15 | } |
147 | | |
148 | 27 | LatExtValue *lat_ext_array_get(const LatExtValue *v, size_t index) { |
149 | 27 | if (v->val.type != VAL_ARRAY || index >= v->val.as.array.len) return NULL; |
150 | 27 | LatExtValue *ev = malloc(sizeof(LatExtValue)); |
151 | 27 | ev->val = value_deep_clone(&v->val.as.array.elems[index]); |
152 | 27 | return ev; |
153 | 27 | } |
154 | | |
155 | 0 | LatExtValue *lat_ext_map_get(const LatExtValue *v, const char *key) { |
156 | 0 | if (v->val.type != VAL_MAP) return NULL; |
157 | 0 | LatValue *found = (LatValue *)lat_map_get(v->val.as.map.map, key); |
158 | 0 | if (!found) return NULL; |
159 | 0 | LatExtValue *ev = malloc(sizeof(LatExtValue)); |
160 | 0 | ev->val = value_deep_clone(found); |
161 | 0 | return ev; |
162 | 0 | } |
163 | | |
164 | | /* ── Cleanup ── */ |
165 | | |
166 | 294 | void lat_ext_free(LatExtValue *v) { |
167 | 294 | if (!v) return; |
168 | 294 | value_free(&v->val); |
169 | 294 | free(v); |
170 | 294 | } |
171 | | |
172 | | /* ── Native closure trampoline ── */ |
173 | | /* |
174 | | * This is the function pointer stored in closure.native_fn. |
175 | | * It wraps calling an LatExtFn: converts args to LatExtValue**, |
176 | | * calls the extension function, and unwraps the result. |
177 | | * |
178 | | * We don't call it directly from call_closure; instead, eval.c's |
179 | | * native dispatch calls ext_call_native() which does the wrapping. |
180 | | */ |
181 | | |
182 | 192 | LatValue ext_call_native(void *fn_ptr, LatValue *args, size_t argc) { |
183 | 192 | LatExtFn fn = (LatExtFn)fn_ptr; |
184 | | |
185 | | /* Wrap args as LatExtValue pointers (stack-allocated wrappers) */ |
186 | 192 | LatExtValue *ext_args_storage = malloc(argc * sizeof(LatExtValue)); |
187 | 192 | LatExtValue **ext_args = malloc(argc * sizeof(LatExtValue *)); |
188 | 498 | for (size_t i = 0; i < argc; i++) { |
189 | 306 | ext_args_storage[i].val = args[i]; |
190 | 306 | ext_args[i] = &ext_args_storage[i]; |
191 | 306 | } |
192 | | |
193 | 192 | LatExtValue *result = fn(ext_args, argc); |
194 | | |
195 | 192 | free(ext_args); |
196 | 192 | free(ext_args_storage); |
197 | | |
198 | 192 | if (!result) return value_nil(); |
199 | 192 | LatValue ret = value_deep_clone(&result->val); |
200 | 192 | lat_ext_free(result); |
201 | 192 | return ret; |
202 | 192 | } |
203 | | |
204 | | /* ── Extension loader ── */ |
205 | | |
206 | | #ifndef __EMSCRIPTEN__ |
207 | | |
208 | 48 | LatValue ext_load(Evaluator *ev, const char *name, char **err) { |
209 | 48 | (void)ev; /* for future use (e.g., passing evaluator context) */ |
210 | | |
211 | | /* Determine library suffix */ |
212 | 48 | #ifdef __APPLE__ |
213 | 48 | const char *suffix = ".dylib"; |
214 | | #else |
215 | | const char *suffix = ".so"; |
216 | | #endif |
217 | | |
218 | | /* Build search paths */ |
219 | 48 | char paths[5][1024]; |
220 | 48 | int path_count = 0; |
221 | | |
222 | | /* 1. ./extensions/<name><suffix> */ |
223 | 48 | snprintf(paths[path_count++], sizeof(paths[0]), |
224 | 48 | "./extensions/%s%s", name, suffix); |
225 | | |
226 | | /* 2. ./extensions/<name>/<name><suffix> */ |
227 | 48 | snprintf(paths[path_count++], sizeof(paths[0]), |
228 | 48 | "./extensions/%s/%s%s", name, name, suffix); |
229 | | |
230 | | /* 3. ~/.lattice/ext/<name><suffix> */ |
231 | 48 | const char *home = getenv("HOME"); |
232 | 48 | if (home) { |
233 | 48 | snprintf(paths[path_count++], sizeof(paths[0]), |
234 | 48 | "%s/.lattice/ext/%s%s", home, name, suffix); |
235 | 48 | } |
236 | | |
237 | | /* 4. $LATTICE_EXT_PATH/<name><suffix> */ |
238 | 48 | const char *ext_path = getenv("LATTICE_EXT_PATH"); |
239 | 48 | if (ext_path) { |
240 | 0 | snprintf(paths[path_count++], sizeof(paths[0]), |
241 | 0 | "%s/%s%s", ext_path, name, suffix); |
242 | 0 | } |
243 | | |
244 | | /* Try each path */ |
245 | 48 | void *handle = NULL; |
246 | 102 | for (int i = 0; i < path_count; i++) { |
247 | 99 | handle = dlopen(paths[i], RTLD_NOW); |
248 | 99 | if (handle) break; |
249 | 99 | } |
250 | | |
251 | 48 | if (!handle) { |
252 | 3 | (void)asprintf(err, "require_ext: cannot find extension '%s' " |
253 | 3 | "(searched ./extensions/, ./extensions/%s/, ~/.lattice/ext/, $LATTICE_EXT_PATH)", |
254 | 3 | name, name); |
255 | 3 | return value_nil(); |
256 | 3 | } |
257 | | |
258 | | /* Look up init function */ |
259 | 45 | LatExtInitFn init_fn = (LatExtInitFn)dlsym(handle, "lat_ext_init"); |
260 | 45 | if (!init_fn) { |
261 | 0 | (void)asprintf(err, "require_ext: extension '%s' has no lat_ext_init()", name); |
262 | 0 | dlclose(handle); |
263 | 0 | return value_nil(); |
264 | 0 | } |
265 | | |
266 | | /* Create context and call init */ |
267 | 45 | LatExtContext ctx; |
268 | 45 | memset(&ctx, 0, sizeof(ctx)); |
269 | 45 | init_fn(&ctx); |
270 | | |
271 | | /* Build a Map of native closures */ |
272 | 45 | LatValue map = value_map_new(); |
273 | 315 | for (size_t i = 0; i < ctx.reg.count; i++) { |
274 | | /* Create a native closure: no params (variadic), no body, no env */ |
275 | 270 | char *pname = strdup("args"); |
276 | 270 | char **param_names = malloc(sizeof(char *)); |
277 | 270 | param_names[0] = pname; |
278 | 270 | LatValue closure = value_closure(param_names, 1, NULL, NULL, NULL, true); |
279 | 270 | closure.as.closure.native_fn = (void *)ctx.reg.fns[i]; |
280 | 270 | free(param_names); |
281 | | |
282 | 270 | lat_map_set(map.as.map.map, ctx.reg.names[i], &closure); |
283 | 270 | free(ctx.reg.names[i]); |
284 | 270 | } |
285 | 45 | free(ctx.reg.names); |
286 | 45 | free(ctx.reg.fns); |
287 | | |
288 | | /* NOTE: we intentionally do NOT dlclose(handle) -- the library must stay |
289 | | * loaded while its functions are callable. The handle leaks, which is |
290 | | * acceptable for the lifetime of the process. */ |
291 | | |
292 | 45 | return map; |
293 | 45 | } |
294 | | |
295 | | #else /* __EMSCRIPTEN__ */ |
296 | | |
297 | | LatValue ext_load(Evaluator *ev, const char *name, char **err) { |
298 | | (void)ev; |
299 | | (void)asprintf(err, "require_ext: native extensions not available in WASM (tried '%s')", name); |
300 | | return value_nil(); |
301 | | } |
302 | | |
303 | | #endif /* __EMSCRIPTEN__ */ |