1/* Copyright JS Foundation and other contributors, http://js.foundation
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include <string.h>
17#include "jerryscript.h"
18#include "jerryscript-ext/module.h"
19
20static const jerry_char_t *module_name_property_name = (jerry_char_t *) "moduleName";
21static const jerry_char_t *module_not_found = (jerry_char_t *) "Module not found";
22static const jerry_char_t *module_name_not_string = (jerry_char_t *) "Module name is not a string";
23
24/**
25 * Create an error related to modules
26 *
27 * Creates an error object of the requested type with the additional property "moduleName" the value of which is a
28 * string containing the name of the module that was requested when the error occurred.
29 *
30 * @return the error
31 */
32static jerry_value_t
33jerryx_module_create_error (jerry_error_t error_type, /**< the type of error to create */
34 const jerry_char_t *message, /**< the error message */
35 const jerry_value_t module_name) /**< the module name */
36{
37 jerry_value_t ret = jerry_create_error (error_type, message);
38
39 jerry_value_t error_object = jerry_get_value_from_error (ret, false);
40 jerry_value_t property_name = jerry_create_string (module_name_property_name);
41
42 jerry_release_value (jerry_set_property (error_object, property_name, module_name));
43
44 jerry_release_value (property_name);
45 jerry_release_value (error_object);
46 return ret;
47} /* jerryx_module_create_error */
48
49/**
50 * Initialize the module manager extension.
51 */
52static void
53jerryx_module_manager_init (void *user_data_p)
54{
55 *((jerry_value_t *) user_data_p) = jerry_create_object ();
56} /* jerryx_module_manager_init */
57
58/**
59 * Deinitialize the module manager extension.
60 */
61static void
62jerryx_module_manager_deinit (void *user_data_p) /**< context pointer to deinitialize */
63{
64 jerry_release_value (*(jerry_value_t *) user_data_p);
65} /* jerryx_module_manager_deinit */
66
67/**
68 * Declare the context data manager for modules.
69 */
70static const jerry_context_data_manager_t jerryx_module_manager =
71{
72 .init_cb = jerryx_module_manager_init,
73 .deinit_cb = jerryx_module_manager_deinit,
74 .bytes_needed = sizeof (jerry_value_t)
75};
76
77/**
78 * Global static entry point to the linked list of available modules.
79 */
80static jerryx_native_module_t *first_module_p = NULL;
81
82void jerryx_native_module_register (jerryx_native_module_t *module_p)
83{
84 module_p->next_p = first_module_p;
85 first_module_p = module_p;
86} /* jerryx_native_module_register */
87
88void jerryx_native_module_unregister (jerryx_native_module_t *module_p)
89{
90 jerryx_native_module_t *parent_p = NULL, *iter_p = NULL;
91
92 for (iter_p = first_module_p; iter_p != NULL; parent_p = iter_p, iter_p = iter_p->next_p)
93 {
94 if (iter_p == module_p)
95 {
96 if (parent_p)
97 {
98 parent_p->next_p = module_p->next_p;
99 }
100 else
101 {
102 first_module_p = module_p->next_p;
103 }
104 module_p->next_p = NULL;
105 }
106 }
107} /* jerryx_native_module_unregister */
108
109/**
110 * Attempt to retrieve a module by name from a cache, and return false if not found.
111 */
112static bool
113jerryx_module_check_cache (jerry_value_t cache, /**< cache from which to attempt to retrieve the module by name */
114 jerry_value_t module_name, /**< JerryScript string value holding the module name */
115 jerry_value_t *result) /**< Resulting value */
116{
117 bool ret = false;
118
119 /* Check if the cache has the module. */
120 jerry_value_t js_has_property = jerry_has_property (cache, module_name);
121
122 /* If we succeed in getting an answer, we examine the answer. */
123 if (!jerry_value_is_error (js_has_property))
124 {
125 bool has_property = jerry_get_boolean_value (js_has_property);
126
127 /* If the module is indeed in the cache, we return it. */
128 if (has_property)
129 {
130 if (result != NULL)
131 {
132 (*result) = jerry_get_property (cache, module_name);
133 }
134 ret = true;
135 }
136 }
137
138 jerry_release_value (js_has_property);
139
140 return ret;
141} /* jerryx_module_check_cache */
142
143/**
144 * Attempt to cache a loaded module.
145 *
146 * @return the module on success, otherwise the error encountered when attempting to cache. In the latter case, the
147 * @p module is released.
148 */
149static jerry_value_t
150jerryx_module_add_to_cache (jerry_value_t cache, /**< cache to which to add the module */
151 jerry_value_t module_name, /**< key at which to cache the module */
152 jerry_value_t module) /**< the module to cache */
153{
154 jerry_value_t ret = jerry_set_property (cache, module_name, module);
155
156 if (jerry_value_is_error (ret))
157 {
158 jerry_release_value (module);
159 }
160 else
161 {
162 jerry_release_value (ret);
163 ret = module;
164 }
165
166 return ret;
167} /* jerryx_module_add_to_cache */
168
169static const jerry_char_t *on_resolve_absent = (jerry_char_t *) "Module on_resolve () must not be NULL";
170
171/**
172 * Declare and define the default module resolver - one which examines what modules are defined in the above linker
173 * section and loads one that matches the requested name, caching the result for subsequent requests using the context
174 * data mechanism.
175 */
176static bool
177jerryx_resolve_native_module (const jerry_value_t canonical_name, /**< canonical name of the module */
178 jerry_value_t *result) /**< [out] where to put the resulting module instance */
179{
180 const jerryx_native_module_t *module_p = NULL;
181
182 jerry_size_t name_size = jerry_get_utf8_string_size (canonical_name);
183 JERRY_VLA (jerry_char_t, name_string, name_size);
184 jerry_string_to_utf8_char_buffer (canonical_name, name_string, name_size);
185
186 /* Look for the module by its name in the list of module definitions. */
187 for (module_p = first_module_p; module_p != NULL; module_p = module_p->next_p)
188 {
189 if (module_p->name_p != NULL
190 && strlen ((char *) module_p->name_p) == name_size
191 && !strncmp ((char *) module_p->name_p, (char *) name_string, name_size))
192 {
193 /* If we find the module by its name we load it and cache it if it has an on_resolve () and complain otherwise. */
194 (*result) = ((module_p->on_resolve_p) ? module_p->on_resolve_p ()
195 : jerryx_module_create_error (JERRY_ERROR_TYPE,
196 on_resolve_absent,
197 canonical_name));
198 return true;
199 }
200 }
201
202 return false;
203} /* jerryx_resolve_native_module */
204
205jerryx_module_resolver_t jerryx_module_native_resolver =
206{
207 .get_canonical_name_p = NULL,
208 .resolve_p = jerryx_resolve_native_module
209};
210
211static void
212jerryx_module_resolve_local (const jerry_value_t name, /**< name of the module to load */
213 const jerryx_module_resolver_t **resolvers_p, /**< list of resolvers */
214 size_t resolver_count, /**< number of resolvers in @p resolvers */
215 jerry_value_t *result) /**< location to store the result, or NULL to remove the module */
216{
217 size_t index;
218 size_t canonical_names_used = 0;
219 jerry_value_t instances;
220 JERRY_VLA (jerry_value_t, canonical_names, resolver_count);
221 jerry_value_t (*get_canonical_name_p) (const jerry_value_t name);
222 bool (*resolve_p) (const jerry_value_t canonical_name,
223 jerry_value_t *result);
224
225 if (!jerry_value_is_string (name))
226 {
227 if (result != NULL)
228 {
229 *result = jerryx_module_create_error (JERRY_ERROR_COMMON, module_name_not_string, name);
230 }
231 goto done;
232 }
233
234 instances = *(jerry_value_t *) jerry_get_context_data (&jerryx_module_manager);
235
236 /**
237 * Establish the canonical name for the requested module. Each resolver presents its own canonical name. If one of
238 * the canonical names matches a cached module, it is returned as the result.
239 */
240 for (index = 0; index < resolver_count; index++)
241 {
242 get_canonical_name_p = (resolvers_p[index] == NULL ? NULL : resolvers_p[index]->get_canonical_name_p);
243 canonical_names[index] = ((get_canonical_name_p == NULL) ? jerry_acquire_value (name)
244 : get_canonical_name_p (name));
245 canonical_names_used++;
246 if (jerryx_module_check_cache (instances, canonical_names[index], result))
247 {
248 /* A NULL for result indicates that we are to delete the module from the cache if found. Let's do that here.*/
249 if (result == NULL)
250 {
251 jerry_delete_property (instances, canonical_names[index]);
252 }
253 goto done;
254 }
255 }
256
257 if (result == NULL)
258 {
259 goto done;
260 }
261
262 /**
263 * Past this point we assume a module is wanted, and therefore result is not NULL. So, we try each resolver until one
264 * manages to resolve the module.
265 */
266 for (index = 0; index < resolver_count; index++)
267 {
268 resolve_p = (resolvers_p[index] == NULL ? NULL : resolvers_p[index]->resolve_p);
269 if (resolve_p != NULL && resolve_p (canonical_names[index], result))
270 {
271 if (!jerry_value_is_error (*result))
272 {
273 *result = jerryx_module_add_to_cache (instances, canonical_names[index], *result);
274 }
275 goto done;
276 }
277 }
278
279 /* If none of the resolvers manage to find the module, complain with "Module not found" */
280 *result = jerryx_module_create_error (JERRY_ERROR_COMMON, module_not_found, name);
281
282done:
283 /* Release the canonical names as returned by the various resolvers. */
284 for (index = 0; index < canonical_names_used; index++)
285 {
286 jerry_release_value (canonical_names[index]);
287 }
288} /* jerryx_module_resolve_local */
289
290/**
291 * Resolve a single module using the module resolvers available in the section declared above and load it into the
292 * current context.
293 *
294 * @p name - name of the module to resolve
295 * @p resolvers - list of resolvers to invoke
296 * @p count - number of resolvers in the list
297 *
298 * @return a jerry_value_t containing one of the followings:
299 * - the result of having loaded the module named @p name, or
300 * - the result of a previous successful load, or
301 * - an error indicating that something went wrong during the attempt to load the module.
302 */
303jerry_value_t
304jerryx_module_resolve (const jerry_value_t name, /**< name of the module to load */
305 const jerryx_module_resolver_t **resolvers_p, /**< list of resolvers */
306 size_t resolver_count) /**< number of resolvers in @p resolvers */
307{
308 /* Set to zero to circumvent fatal warning. */
309 jerry_value_t ret = 0;
310 jerryx_module_resolve_local (name, resolvers_p, resolver_count, &ret);
311 return ret;
312} /* jerryx_module_resolve */
313
314void
315jerryx_module_clear_cache (const jerry_value_t name, /**< name of the module to remove, or undefined */
316 const jerryx_module_resolver_t **resolvers_p, /**< list of resolvers */
317 size_t resolver_count) /**< number of resolvers in @p resolvers */
318{
319 void *instances_p = jerry_get_context_data (&jerryx_module_manager);
320
321 if (jerry_value_is_undefined (name))
322 {
323 /* We were requested to clear the entire cache, so we bounce the context data in the most agnostic way possible. */
324 jerryx_module_manager.deinit_cb (instances_p);
325 jerryx_module_manager.init_cb (instances_p);
326 return;
327 }
328
329 /* Delete the requested module from the cache if it's there. */
330 jerryx_module_resolve_local (name, resolvers_p, resolver_count, NULL);
331} /* jerryx_module_clear_cache */
332