Coverage Report

Created: 2026-02-23 20:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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__ */