MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include "esm/commonjs.h"
2#include "esm/loader.h"
3
4#include "internal.h"
5#include "reactor.h"
6#include "errors.h"
7
8#include "silver/compiler.h"
9#include "silver/engine.h"
10
11#include <libgen.h>
12#include <string.h>
13#include <stdlib.h>
14
15static ant_value_t esm_cjs_require(ant_t *js, ant_value_t *args, int nargs) {
16 if (nargs < 1 || vtype(args[0]) != T_STR)
17 return js_mkerr(js, "require() expects a string specifier");
18
19 ant_value_t fn = js_getcurrentfunc(js);
20 ant_value_t data = js_get_slot(fn, SLOT_DATA);
21 const char *base_path = js_module_eval_active_filename(js);
22
23 if (vtype(data) == T_STR) {
24 ant_offset_t path_len = 0;
25 ant_offset_t path_off = vstr(js, data, &path_len);
26 base_path = (const char *)(uintptr_t)(path_off);
27 }
28
29 ant_value_t ns = js_esm_import_sync_from_require(js, args[0], base_path);
30 if (is_err(ns)) return ns;
31
32 if (vtype(ns) == T_OBJ) {
33 ant_value_t default_export = js_get_slot(ns, SLOT_DEFAULT);
34 if (vtype(default_export) != T_UNDEF) return default_export;
35 }
36
37 return ns;
38}
39
40static ant_value_t esm_cjs_require_resolve(ant_t *js, ant_value_t *args, int nargs) {
41 if (nargs < 1 || vtype(args[0]) != T_STR) {
42 return js_mkerr(js, "require.resolve() expects a string specifier");
43 }
44
45 ant_value_t fn = js_getcurrentfunc(js);
46 ant_value_t data = js_get_slot(fn, SLOT_DATA);
47 const char *base_path = js_module_eval_active_filename(js);
48
49 if (vtype(data) == T_STR) {
50 ant_offset_t data_len = 0;
51 ant_offset_t data_off = vstr(js, data, &data_len);
52 base_path = (const char *)(uintptr_t)(data_off);
53 }
54
55 ant_value_t resolved = js_esm_resolve_specifier_require(js, args[0], base_path);
56 if (is_err(resolved)) return resolved;
57 if (vtype(resolved) != T_STR) return resolved;
58
59 ant_offset_t len = 0;
60 ant_offset_t off = vstr(js, resolved, &len);
61
62 const char *s = (const char *)(uintptr_t)(off);
63 static const char *prefix = "file://";
64
65 if ((size_t)len >= strlen(prefix) && strncmp(s, prefix, strlen(prefix)) == 0) {
66 const char *path_part = s + strlen(prefix);
67 size_t path_len = (size_t)len - strlen(prefix);
68 return js_mkstr(js, path_part, path_len);
69 }
70
71 return resolved;
72}
73
74static bool copy_own_prop(
75 ant_t *js, ant_value_t dst, ant_value_t src,
76 const char *key, size_t key_len, ant_value_t *err
77) {
78 ant_value_t value = js_mkundef();
79
80 if (js_try_get_own_data_prop(js, src, key, key_len, &value)) {
81 ant_value_t res = setprop_cstr(js, dst, key, key_len, value);
82 if (is_err(res)) { *err = res; return false; }
83 return true;
84 }
85
86 prop_meta_t meta;
87 if (lookup_string_prop_meta(js, src, key, key_len, &meta) && (meta.has_getter || meta.has_setter)) {
88 ant_value_t ns = js_as_obj(dst);
89 int flags = (meta.enumerable ? JS_DESC_E : 0) | (meta.configurable ? JS_DESC_C : 0);
90 if (meta.has_getter) js_set_getter_desc(js, ns, key, key_len, meta.getter, flags);
91 if (meta.has_setter) js_set_setter_desc(js, ns, key, key_len, meta.setter, flags);
92 return true;
93 }
94
95 value = js_get(js, src, key);
96 if (is_err(value)) { *err = value; return false; }
97 if (vtype(value) == T_UNDEF) return true;
98
99 ant_value_t res = setprop_cstr(js, dst, key, key_len, value);
100 if (is_err(res)) { *err = res; return false; }
101
102 return true;
103}
104
105static ant_value_t esm_populate_cjs_namespace(ant_t *js, ant_value_t ns, ant_value_t exports_val) {
106 ant_value_t set_default = setprop_cstr(js, ns, "default", 7, exports_val);
107 if (is_err(set_default)) return set_default;
108
109 js_set_slot_wb(js, ns, SLOT_DEFAULT, exports_val);
110 if (!is_object_type(exports_val)) return js_mkundef();
111
112 ant_iter_t iter = js_prop_iter_begin(js, exports_val);
113 const char *key = NULL;
114 size_t key_len = 0;
115
116 while (js_prop_iter_next(&iter, &key, &key_len, NULL)) {
117 if (key_len == 7 && memcmp(key, "default", 7) == 0) continue;
118
119 ant_value_t err = js_mkundef();
120 if (!copy_own_prop(js, ns, exports_val, key, key_len, &err)) {
121 if (is_err(err)) { js_prop_iter_end(&iter); return err; }
122 }}
123
124 js_prop_iter_end(&iter);
125 return js_mkundef();
126}
127
128static ant_value_t esm_eval_commonjs_function(
129 ant_t *js,
130 const char *code,
131 size_t code_len,
132 ant_value_t require_fn,
133 ant_value_t module_obj,
134 ant_value_t exports_obj,
135 ant_value_t filename_val,
136 ant_value_t dirname_val
137) {
138 static const sv_param_t cjs_params[] = {
139 SV_PARAM("require"),
140 SV_PARAM("module"),
141 SV_PARAM("exports"),
142 SV_PARAM("__filename"),
143 SV_PARAM("__dirname"),
144 };
145
146 sv_func_t *compiled = sv_compile_function_with_params(
147 js, cjs_params,
148 (int)(sizeof(cjs_params) / sizeof(cjs_params[0])),
149 code, code_len, false
150 );
151
152 if (!compiled) {
153 if (js->thrown_exists) return mkval(T_ERR, 0);
154 return js_mkerr_typed(js, JS_ERR_INTERNAL | JS_ERR_NO_STACK, "Unexpected compile error");
155 }
156
157 js_clear_error_site(js);
158 ant_value_t args[] = {require_fn, module_obj, exports_obj, filename_val, dirname_val};
159 return sv_execute_entry(js->vm, compiled, exports_obj, args, 5);
160}
161
162ant_value_t esm_load_commonjs_module(
163 ant_t *js,
164 const char *module_path, const char *code,
165 size_t code_len, ant_value_t ns
166) {
167 char *path_copy = strdup(module_path);
168 if (!path_copy) return js_mkerr(js, "OOM loading CommonJS module");
169
170 ant_value_t object_proto = js->sym.object_proto;
171 ant_value_t module_obj = js_mkobj(js);
172 ant_value_t exports_obj = js_mkobj(js);
173
174 if (is_object_type(object_proto)) {
175 js_set_proto_init(module_obj, object_proto);
176 js_set_proto_init(exports_obj, object_proto);
177 }
178
179 js_set(js, module_obj, "exports", exports_obj);
180 js_set(js, module_obj, "loaded", js_false);
181 js_set(js, module_obj, "id", js_mkstr(js, module_path, strlen(module_path)));
182 js_set(js, module_obj, "filename", js_mkstr(js, module_path, strlen(module_path)));
183
184 ant_value_t require_fn = js_heavy_mkfun(
185 js, esm_cjs_require,
186 js_mkstr(js, module_path, strlen(module_path))
187 );
188
189 ant_value_t require_resolve_fn = js_heavy_mkfun(
190 js, esm_cjs_require_resolve,
191 js_mkstr(js, module_path, strlen(module_path))
192 );
193
194 js_set(js, require_fn, "resolve", require_resolve_fn);
195
196 char *dir = dirname(path_copy);
197 ant_value_t dirname_val = js_mkstr(js, dir, strlen(dir));
198 ant_value_t filename_val = js_mkstr(js, module_path, strlen(module_path));
199
200 const char *prev_filename = js->filename;
201 js_set_filename(js, module_path);
202
203 ant_value_t result = esm_eval_commonjs_function(
204 js, code, code_len,
205 require_fn, module_obj, exports_obj,
206 filename_val, dirname_val
207 );
208
209 if (vtype(result) == T_PROMISE) js_run_event_loop(js);
210 js_set(js, module_obj, "loaded", js_true);
211 ant_value_t exports_val = js_get(js, module_obj, "exports");
212
213 if (!is_err(result) && !js->thrown_exists) {
214 ant_value_t ns_res = esm_populate_cjs_namespace(js, ns, exports_val);
215 if (is_err(ns_res)) result = ns_res;
216 }
217
218 js_set_filename(js, prev_filename);
219 free(path_copy);
220
221 if (is_err(result)) return result;
222 return exports_val;
223}