Gabriel "_|Nix|_" Schulhof 66b2a7670f New extension: module (#1863)
This extension provides the following facilities:
  - registering modules,
  - module resolvers, and
  - an API for retrieving a module instance given its name.

A module is defined as a global static structure containing a pointer
to a string which is the name of the module, and a pointer to a function
which will be called when an instance of the module is needed.

A module resolver is a function that accepts a string holding the name
of the module and returns a `jerry_value_t` in an out-parameter and
`true` if the module was found, or `false` if it was not. If it returns
`true` and the out-parameter has the error flag set then the API will
pass it through without caching.

This extension provides a built-in module resolver which attempts to
load modules that follow the above module definition.

The API provided by this extension invokes all module resolvers it
receives in sequence to attempt to resolve the name of a single module.
After one resolver returns `true` and a `jerry_value_t` that represents
the module the API stops iterating over the remaining resolvers and
caches the value if its error flag is not set. It then returns the
`jerry_value_t`. The API will return a `jerry_value_t` containing an
error indicating that the module was not found if it reaches the end of
the list of resolvers. The error it returns has an extra property
`"moduleName"` the value of which is a string containing the name of the
module that the API was asked to resolve.

JerryScript-DCO-1.0-Signed-off-by: Gabriel Schulhof gabriel.schulhof@intel.com
2017-07-18 15:54:24 +02:00

218 lines
7.5 KiB
C

/* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string.h>
#include "jerryscript.h"
#include "jerryscript-ext/module.h"
static const jerry_char_t *module_name_property_name = (jerry_char_t *) "moduleName";
static const jerry_char_t *module_not_found = (jerry_char_t *) "Module not found";
/**
* Create an error related to modules
*
* Creates an error object of the requested type with the additional property "moduleName" the value of which is a
* string containing the name of the module that was requested when the error occurred.
*
* @return the error
*/
static jerry_value_t
jerryx_module_create_error (jerry_error_t error_type, /**< the type of error to create */
const jerry_char_t *message, /**< the error message */
const jerry_char_t *module_name) /**< the module name */
{
jerry_value_t ret = jerry_create_error (error_type, message);
jerry_value_t property_name = jerry_create_string (module_name_property_name);
jerry_value_t property_value = jerry_create_string_from_utf8 (module_name);
jerry_release_value (jerry_set_property (ret, property_name, property_value));
jerry_release_value (property_name);
jerry_release_value (property_value);
return ret;
} /* jerryx_module_create_error */
/**
* Initialize the module manager extension.
*/
static void
jerryx_module_manager_init (void *user_data_p)
{
*((jerry_value_t *) user_data_p) = jerry_create_object ();
} /* jerryx_module_manager_init */
/**
* Deinitialize the module manager extension.
*/
static void
jerryx_module_manager_deinit (void *user_data_p) /**< context pointer to deinitialize */
{
jerry_release_value (*(jerry_value_t *) user_data_p);
} /* jerryx_module_manager_deinit */
/**
* Declare the context data manager for modules.
*/
static const jerry_context_data_manager_t jerryx_module_manager =
{
.init_cb = jerryx_module_manager_init,
.deinit_cb = jerryx_module_manager_deinit,
.bytes_needed = sizeof (jerry_value_t)
};
/**
* Declare the linker section where module definitions are stored.
*/
JERRYX_SECTION_DECLARE (jerryx_modules, jerryx_native_module_t)
/**
* Attempt to retrieve a module by name from a cache, and return false if not found.
*/
static bool
jerryx_module_check_cache (jerry_value_t cache, /**< cache from which to attempt to retrieve the module by name */
jerry_value_t module_name, /**< JerryScript string value holding the module name */
jerry_value_t *result) /**< Resulting value */
{
bool ret = false;
/* Check if the cache has the module. */
jerry_value_t js_has_property = jerry_has_property (cache, module_name);
/* If we succeed in getting an answer, we examine the answer. */
if (!jerry_value_has_error_flag (js_has_property))
{
bool has_property = jerry_get_boolean_value (js_has_property);
/* If the module is indeed in the cache, we return it. */
if (has_property)
{
(*result) = jerry_get_property (cache, module_name);
ret = true;
}
}
jerry_release_value (js_has_property);
return ret;
} /* jerryx_module_check_cache */
/**
* Attempt to cache a loaded module.
*
* @return the module on success, otherwise the error encountered when attempting to cache. In the latter case, the
* @p module is released.
*/
static jerry_value_t
jerryx_module_add_to_cache (jerry_value_t cache, /**< cache to which to add the module */
jerry_value_t module_name, /**< key at which to cache the module */
jerry_value_t module) /**< the module to cache */
{
jerry_value_t ret = jerry_set_property (cache, module_name, module);
if (jerry_value_has_error_flag (ret))
{
jerry_release_value (module);
}
else
{
jerry_release_value (ret);
ret = module;
}
return ret;
} /* jerryx_module_add_to_cache */
#ifdef JERRYX_NATIVE_MODULES_SUPPORTED
static const jerry_char_t *on_resolve_absent = (jerry_char_t *) "Module on_resolve () must not be NULL";
/**
* Declare and define the default module resolver - one which examines what modules are defined in the above linker
* section and loads one that matches the requested name, caching the result for subsequent requests using the context
* data mechanism.
*/
bool
jerryx_module_native_resolver (const jerry_char_t *name, /**< name of the module */
jerry_value_t *result) /**< [out] where to put the resulting module instance */
{
int index;
const jerryx_native_module_t *module_p = NULL;
/* Look for the module by its name in the list of module definitions. */
for (index = 0, module_p = &__start_jerryx_modules[0];
&__start_jerryx_modules[index] < __stop_jerryx_modules;
index++, module_p = &__start_jerryx_modules[index])
{
if (module_p->name != NULL && !strcmp ((char *) module_p->name, (char *) name))
{
/* If we find the module by its name we load it and cache it if it has an on_resolve () and complain otherwise. */
(*result) = ((module_p->on_resolve) ? module_p->on_resolve ()
: jerryx_module_create_error (JERRY_ERROR_TYPE, on_resolve_absent, name));
return true;
}
}
return false;
} /* jerryx_module_native_resolver */
#endif /* JERRYX_NATIVE_MODULES_SUPPORTED */
/**
* Resolve a single module using the module resolvers available in the section declared above and load it into the
* current context.
*
* @p name - name of the module to resolve
* @p resolvers - list of resolvers to invoke
* @p count - number of resolvers in the list
*
* @return a jerry_value_t containing one of the followings:
* - the result of having loaded the module named @p name, or
* - the result of a previous successful load, or
* - an error indicating that something went wrong during the attempt to load the module.
*/
jerry_value_t
jerryx_module_resolve (const jerry_char_t *name, /**< name of the module to load */
jerryx_module_resolver_t *resolvers_p, /**< list of resolvers */
size_t resolver_count) /**< number of resolvers in @p resolvers */
{
size_t index;
jerry_value_t ret;
jerry_value_t instances = *(jerry_value_t *) jerry_get_context_data (&jerryx_module_manager);
jerry_value_t module_name = jerry_create_string_from_utf8 (name);
/* Return the cached instance if present. */
if (jerryx_module_check_cache (instances, module_name, &ret))
{
goto done;
}
/* Try each resolver until one manages to find the module. */
for (index = 0; index < resolver_count; index++)
{
if ((*resolvers_p[index]) (name, &ret))
{
if (!jerry_value_has_error_flag (ret))
{
ret = jerryx_module_add_to_cache (instances, module_name, ret);
}
goto done;
}
}
/* If none of the resolvers manage to find the module, complain with "Module not found" */
ret = jerryx_module_create_error (JERRY_ERROR_COMMON, module_not_found, name);
done:
jerry_release_value (module_name);
return ret;
} /* jerryx_module_resolve */