diff --git a/docs/12.EXT-REFERENCE-MODULE.md b/docs/12.EXT-REFERENCE-MODULE.md new file mode 100644 index 000000000..f28679f64 --- /dev/null +++ b/docs/12.EXT-REFERENCE-MODULE.md @@ -0,0 +1,186 @@ +# Module API + +This is a JerryScript extension that provides a means of loading modules. Fundamentally, a module is a name (stored as +a string) that resolves to a `jerry_value_t`. This extension provides the function `jerryx_module_resolve()` which +accepts the name of the module being requested as well as an array of so-called "resolvers" - functions which satisfy +the signature `jerryx_module_resolver_t`. The resolvers in the list are called in sequence until one of them returns +`true` and a `jerry_value_t` in its out parameter. The value is cached if it is not an error, so subsequent requests +for the same name will not result in additional calls to the resolvers. + +The purpose of having resolvers is to be able to account for the fact that different types of modules may be structured +differently and thus, for each type of module a module resolver must be supplied at the point where an instance of that +type of module is requested. + +Additionally, this extension provides a means of easily defining so-called "native" JerryScript modules which can be +resolved using the JerryScript native module resolver `jerryx_module_native_resolver()`, which can be passed to +`jerryx_module_resolve()`. Note, however, that native JerryScript modules are only supported and +`jerryx_module_native_resolver()` is only compiled in if compiler support for `__attribute__` extensions is present. In +effect this means that native JerryScript modules are available only when this extension is built with GCC or +LLVM/clang. In the absence of such support, you may construct alternative module systems and provide your own resolver +to `jerryx_module_resolve()`. + +`jerryscript-ext/module.h` defines the preprocessor directive `JERRYX_NATIVE_MODULES_SUPPORTED` only if support for +native JerryScript modules is available. + +## jerryx_module_resolve + +**Summary** + +Load a copy of a module into the current context or return one that was already loaded if it is found. + +Each function in `resolvers_p` will be called in sequence until one returns `true` and fills out its out-parameter with +the `jerry_value_t` representing the requested module. If the `jerry_value_t` does not have the error flag set it will +be cached. Thus, on subsequent calls with the same value for `name`, none of the functions in `resolvers_p` will be +called. + +**Prototype** + +```c +jerry_value_t +jerryx_module_resolve (const jerry_char_t *name, + jerryx_module_resolver_t *resolvers_p, + size_t resolver_count); +``` + +- `name` - the name of the module to load +- `resolvers_p` - the list of resolvers to call in sequence +- `resolver_count` - the number of resolvers in `resolvers_p` +- return value - `jerry_value_t` representing the module that was loaded, or the error that occurred in the process. + + +## jerryx_module_native_resolver + +**Summary** + +The resolver for JerryScript modules. A pointer to this function can be passed in the second parameter to +`jerryx_module_resolve` to search for the module among the JerryScript modules built into the binary. This function is +available only if the preprocessor directive `JERRYX_NATIVE_MODULES_SUPPORTED` is defined. + +**Prototype** + +```c +bool +jerryx_module_native_resolver (const jerry_char_t *name, + jerry_value_t *result) +``` +- `name` - the name of the module to find +- `result` - out - place where to store the resulting module instance +- return value - `true` if the module was found and stored in `result`, and `false` otherwise + + +# Module data types + +## jerryx_native_module_on_resolve_t + +**Summary** + +Function pointer type for a function that will create an instance of a native module. This type is only defined if the +preprocessor directive `JERRYX_NATIVE_MODULES_SUPPORTED` is defined. + +**Prototype** + +```c +typedef jerry_value_t (*jerryx_native_module_on_resolve_t) (void); +``` + +## jerryx_module_resolver_t + +**Summary** + +Function pointer type for a module resolver + +**Prototype** + +```c +typedef bool (*jerryx_module_resolver_t) (const jerry_char_t *name, jerry_value_t *result); +``` + +**Example** +```c +bool +load_and_evaluate_js_file (const jerry_char_t *name, jerry_value_t *result) +{ + bool return_value = false; + char *js_file_contents = NULL; + int file_size = 0; + FILE *js_file = fopen (name, "r"); + + if (js_file) + { + /* We have successfully opened the file. Now, we establish its size. */ + file_size = fseek (js_file, 0, SEEK_END); + fseek (js_file, 0, SEEK_SET); + + /* We allocate enough memory to store the contents of the file. */ + js_file_contents = malloc (file_size); + if (js_file_contents) + { + /* We read the file into memory and call jerry_eval (), assigning the result to the out-parameter. */ + fread (js_file_contents, file_size, 1, js_file); + (*result) = jerry_eval (js_file_contents, file_size, false); + + /* We release the memory holding the contents of the file. */ + free (js_file_contents); + return_value = true; + } + + /* We close the file. */ + fclose (js_file); + } + + return return_value; +} +``` + +We can now load JavaScript files: +```c +static const jerryx_module_resolver_t resolvers = +{ + /* Consult the JerryScript module resolver first, in case the requested module is a compiled-in JerryScript module. */ + jerryx_module_native_resolver, + + /* + * If the requested module is not a JerryScript module, assume it is a JavaScript file on disk and use the above- + * defined JavaScript file loader to load it. + */ + load_and_evaluate_js_file +}; +jerry_value_t js_module = jerryx_module_resolve (requested_module, resolvers, 2); +``` + +# Module helper macros + +## JERRYX_NATIVE_MODULE + +**Summary** + +Helper macro to define a JerryScript module. Currently stores the name of the module and its initializer in an +executable linker section. This macro is available only if the preprocessor directive `JERRYX_NATIVE_MODULES_SUPPORTED` +is defined. + +**Note**: The helper macro must appear at the bottom of a source file, and no semicolon must follow it. + +**Prototype** +```c +#define JERRYX_NATIVE_MODULE(module_name, on_resolve_cb) +``` + +- `module_name` - the name of the module without quotes +- `on_resolve_cb` - the function of type `jerryx_native_module_on_resolve_t` that will be called when the module needs to be +loaded. + +**Example** + +```c +#include "jerryscript.h" +#include "jerryscript-ext/module.h" + +static jerry_value_t +my_module_on_resolve (void) +{ + return jerry_create_external_function (very_useful_function); +} /* my_module_on_resolve */ + +/* Note that there is no semicolon at the end of the next line. This is how it must be. */ +JERRYX_NATIVE_MODULE (my_module, my_module_on_resolve) +``` diff --git a/jerry-ext/CMakeLists.txt b/jerry-ext/CMakeLists.txt index 571411558..ade315349 100644 --- a/jerry-ext/CMakeLists.txt +++ b/jerry-ext/CMakeLists.txt @@ -21,10 +21,12 @@ set(INCLUDE_EXT "${CMAKE_CURRENT_SOURCE_DIR}/include") # Source directories file(GLOB SOURCE_EXT_ARG arg/*.c) +file(GLOB SOURCE_EXT_MODULE module/*.c) file(GLOB SOURCE_EXT_HANDLER handler/*.c) set(SOURCE_EXT ${SOURCE_EXT_ARG} + ${SOURCE_EXT_MODULE} ${SOURCE_EXT_HANDLER}) add_library(${JERRY_EXT_NAME} STATIC ${SOURCE_EXT}) diff --git a/jerry-ext/include/jerryscript-ext/module.h b/jerry-ext/include/jerryscript-ext/module.h new file mode 100644 index 000000000..00f22b656 --- /dev/null +++ b/jerry-ext/include/jerryscript-ext/module.h @@ -0,0 +1,74 @@ +/* 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. + */ + +#ifndef JERRYX_MODULE_H +#define JERRYX_MODULE_H + +#include "jerryscript.h" + +#ifdef __GNUC__ +#define JERRYX_NATIVE_MODULES_SUPPORTED +#endif /* __GNUC__ */ + +#ifdef JERRYX_NATIVE_MODULES_SUPPORTED +#include "jerryscript-ext/section.impl.h" + +/** + * Declare the signature for the module initialization function. + */ +typedef jerry_value_t (*jerryx_native_module_on_resolve_t) (void); + +/** + * Declare the structure used to define a module. One should only make use of this structure via the + * JERRYX_NATIVE_MODULE macro declared below. + */ +typedef struct +{ + jerry_char_t *name; /**< name of the module */ + jerryx_native_module_on_resolve_t on_resolve; /**< function that returns a new instance of the module */ +} jerryx_native_module_t; + +/** + * Declare a helper macro that expands to the declaration of a variable of type jerryx_native_module_t placed into the + * specially-named linker section "jerryx_modules" where the JerryScript module resolver + * jerryx_module_native_resolver () will look for it. + */ +#define JERRYX_NATIVE_MODULE(module_name, on_resolve_cb) \ + static const jerryx_native_module_t _module JERRYX_SECTION_ATTRIBUTE(jerryx_modules) = \ + { \ + .name = ((jerry_char_t *) #module_name), \ + .on_resolve = (on_resolve_cb) \ + }; + +/** + * Declare the JerryScript module resolver so that it may be added to an array of jerryx_module_resolver_t items and + * thus passed to jerryx_module_resolve. + */ +bool jerryx_module_native_resolver (const jerry_char_t *name, jerry_value_t *result); + +#endif /* JERRYX_NATIVE_MODULES_SUPPORTED */ + +/** + * Declare the function pointer type for module resolvers. + */ +typedef bool (*jerryx_module_resolver_t) (const jerry_char_t *name, jerry_value_t *result); + +/** + * Load a copy of a module into the current context using the provided module resolvers, or return one that was already + * loaded if it is found. + */ +jerry_value_t jerryx_module_resolve (const jerry_char_t *name, jerryx_module_resolver_t *resolvers, size_t count); + +#endif /* !JERRYX_MODULE_H */ diff --git a/jerry-ext/include/jerryscript-ext/section.impl.h b/jerry-ext/include/jerryscript-ext/section.impl.h new file mode 100644 index 000000000..530d021c2 --- /dev/null +++ b/jerry-ext/include/jerryscript-ext/section.impl.h @@ -0,0 +1,56 @@ +/* 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. + */ + +#ifndef JERRYX_SECTION_IMPL_H +#define JERRYX_SECTION_IMPL_H + +/** + * Define the name of a section + * + * @param name the name of the section without quotes + */ +#ifdef __MACH__ +#define JERRYX_SECTION_NAME(name) "__DATA," #name +#else /* !__MACH__ */ +#define JERRYX_SECTION_NAME(name) #name +#endif /* __MACH__ */ + +/** + * Expands to the proper __attribute__(()) qualifier for appending a variable to a section. + */ +#define JERRYX_SECTION_ATTRIBUTE(name) \ + __attribute__ ((used, section (JERRYX_SECTION_NAME (name)), aligned (sizeof (void *)))) + +/** + * Declare references to a section that contains an array of items. + * + * @param name the name of the section (without quotes) + * @param type the type of the elements stored in the array + * + * This macro declares two extern const variables such that their type is an array of @p type and their names are + * constructed by prefixing @p name with "__start_" and "__stop_". They evaluate to the starting and ending address + * of the section @p name. + */ +#ifdef __MACH__ +#define JERRYX_SECTION_DECLARE(name, type) \ + extern const type __start_ ## name[] __asm("section$start$__DATA$" #name); \ + extern const type __stop_ ## name[] __asm("section$end$__DATA$" #name); +#else /* !__MACH__ */ +#define JERRYX_SECTION_DECLARE(name, type) \ + extern const type __start_ ## name[] __attribute__((weak)); \ + extern const type __stop_ ## name[] __attribute__((weak)); +#endif /* __MACH__ */ + +#endif /* !JERRYX_SECTION_IMPL_H */ diff --git a/jerry-ext/module/module.c b/jerry-ext/module/module.c new file mode 100644 index 000000000..917cf5dd1 --- /dev/null +++ b/jerry-ext/module/module.c @@ -0,0 +1,217 @@ +/* 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 +#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 */ diff --git a/tests/unit-ext/CMakeLists.txt b/tests/unit-ext/CMakeLists.txt index f9ab1de04..e7e95d7ab 100644 --- a/tests/unit-ext/CMakeLists.txt +++ b/tests/unit-ext/CMakeLists.txt @@ -15,6 +15,8 @@ cmake_minimum_required (VERSION 2.8.12) project (unit-ext C) +set(INCLUDE_UNIT_EXT ${CMAKE_CURRENT_SOURCE_DIR}) + # Unit tests main modules file(GLOB SOURCE_UNIT_TEST_EXT_MODULES *.c) @@ -33,3 +35,10 @@ foreach(SOURCE_UNIT_TEST_EXT ${SOURCE_UNIT_TEST_EXT_MODULES}) add_dependencies(unittests-ext ${TARGET_NAME}) endforeach() + +file(GLOB CONTENTS_UNIT_TEST_EXT *) +foreach(CONTENT_UNIT_TEST_EXT ${CONTENTS_UNIT_TEST_EXT}) + if(IS_DIRECTORY ${CONTENT_UNIT_TEST_EXT}) + add_subdirectory(${CONTENT_UNIT_TEST_EXT}) + endif() +endforeach() diff --git a/tests/unit-ext/module/CMakeLists.txt b/tests/unit-ext/module/CMakeLists.txt new file mode 100644 index 000000000..233b71274 --- /dev/null +++ b/tests/unit-ext/module/CMakeLists.txt @@ -0,0 +1,24 @@ +# 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. + +cmake_minimum_required (VERSION 2.8.12) +set(JERRYX_MODULE_UNITTEST_NAME unit-test-jerry-module) +project (${JERRYX_MODULE_UNITTEST_NAME} C) + +file(GLOB JERRYX_MODULE_UNIT_TEST_SOURCES *.c) + +add_executable(${JERRYX_MODULE_UNITTEST_NAME} ${JERRYX_MODULE_UNIT_TEST_SOURCES}) +set_property(TARGET ${JERRYX_MODULE_UNITTEST_NAME} PROPERTY LINK_FLAGS "${LINKER_FLAGS_COMMON}") +target_link_libraries(${JERRYX_MODULE_UNITTEST_NAME} jerry-ext jerry-core jerry-port-default-minimal) +target_include_directories(${JERRYX_MODULE_UNITTEST_NAME} PRIVATE ${INCLUDE_UNIT_EXT}) diff --git a/tests/unit-ext/module/jerry-module-test.c b/tests/unit-ext/module/jerry-module-test.c new file mode 100644 index 000000000..7fe3c22c5 --- /dev/null +++ b/tests/unit-ext/module/jerry-module-test.c @@ -0,0 +1,168 @@ +/* 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 + +#include "jerryscript.h" +#include "test-common.h" +#include "jerryscript-ext/module.h" + +#ifdef JERRYX_NATIVE_MODULES_SUPPORTED + +/* Load a module. */ +const char eval_string1[] = "require ('my_custom_module');"; + +/* Load a module using a different resolver. */ +const char eval_string2[] = "require ('differently-handled-module');"; + +/* Load a broken module using the built-in resolver. */ +const char eval_string3[] = +"(function() {" +" var theError;" +" try {" +" require ('my_broken_module');" +" } catch (anError) {" +" theError = anError;" +" }" +" return (((theError.message === 'Module on_resolve () must not be NULL') &&" +" (theError.moduleName === 'my_broken_module') &&" +" (theError instanceof TypeError)) ? 1 : 0);" +"}) ();"; + +/* Load a non-existent module. */ +const char eval_string4[] = +"(function() {" +" var theError;" +" try {" +" require ('some_missing_module_xyzzy');" +" } catch (anError) {" +" theError = anError;" +" }" +" return (((theError.message === 'Module not found') &&" +" (theError.moduleName === 'some_missing_module_xyzzy')) ? 1 : 0);" +"}) ();"; + +/* Make sure the result of a module load is cached. */ +const char eval_string5[] = +"(function() {" +" var x = require('cache-check');" +" var y = require('cache-check');" +" return x === y ? 1 : 0;" +"}) ();"; + +static bool +resolve_differently_handled_module (const jerry_char_t *name, + jerry_value_t *result) +{ + if (!strcmp ((char *) name, "differently-handled-module")) + { + (*result) = jerry_create_number (29); + return true; + } + return false; +} /* resolve_differently_handled_module */ + +/* + * Define module "cache-check" via its own resolver as an empty object. Since objects are accessible only via references + * we can strictly compare the object returned on subsequent attempts at loading "cache-check" with the object returned + * on the first attempt and establish that the two are in fact the same object - which in turn shows that caching works. + */ +static bool +cache_check (const jerry_char_t *name, + jerry_value_t *result) +{ + if (!strcmp ((char *) name, "cache-check")) + { + (*result) = jerry_create_object (); + return true; + } + return false; +} /* cache_check */ + +static jerryx_module_resolver_t resolvers[3] = +{ + jerryx_module_native_resolver, + resolve_differently_handled_module, + cache_check +}; + +static jerry_value_t +handle_require (const jerry_value_t js_function, + const jerry_value_t this_val, + const jerry_value_t args_p[], + const jerry_length_t args_count) +{ + (void) js_function; + (void) this_val; + (void) args_count; + + jerry_value_t return_value = 0; + jerry_char_t module_name[256] = ""; + jerry_size_t bytes_copied = 0; + + TEST_ASSERT (args_count == 1); + bytes_copied = jerry_string_to_char_buffer (args_p[0], module_name, 256); + if (bytes_copied < 256) + { + module_name[bytes_copied] = 0; + return_value = jerryx_module_resolve (module_name, resolvers, 3); + } + + return return_value; +} /* handle_require */ + +static void +assert_number (jerry_value_t js_value, double expected_result) +{ + TEST_ASSERT (!jerry_value_has_error_flag (js_value)); + TEST_ASSERT (jerry_get_number_value (js_value) == expected_result); +} /* assert_number */ + +static void +eval_one (const char *the_string, double expected_result) +{ + jerry_value_t js_eval_result = jerry_eval ((const jerry_char_t *) the_string, strlen (the_string), true); + assert_number (js_eval_result, expected_result); + jerry_release_value (js_eval_result); +} /* eval_one */ + +int +main (int argc, char **argv) +{ + (void) argc; + (void) argv; + jerry_value_t js_global = 0, js_function = 0, js_property_name = 0; + + jerry_init (JERRY_INIT_EMPTY); + + js_global = jerry_get_global_object (); + js_function = jerry_create_external_function (handle_require); + js_property_name = jerry_create_string ((const jerry_char_t *) "require"); + jerry_set_property (js_global, js_property_name, js_function); + + eval_one (eval_string1, 42); + eval_one (eval_string2, 29); + eval_one (eval_string3, 1); + eval_one (eval_string4, 1); + eval_one (eval_string5, 1); + + jerry_release_value (js_property_name); + jerry_release_value (js_function); + jerry_release_value (js_global); + + jerry_cleanup (); +} /* main */ + +#endif /* JERRYX_NATIVE_MODULES_SUPPORTED */ diff --git a/tests/unit-ext/module/my-broken-module.c b/tests/unit-ext/module/my-broken-module.c new file mode 100644 index 000000000..59ff32377 --- /dev/null +++ b/tests/unit-ext/module/my-broken-module.c @@ -0,0 +1,24 @@ +/* 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 "jerryscript.h" +#include "jerryscript-ext/module.h" + +#ifdef JERRYX_NATIVE_MODULES_SUPPORTED +/* + * A broken module to test that the loader complains about the absence of on_resolve () + */ +JERRYX_NATIVE_MODULE (my_broken_module, NULL) +#endif /* JERRYX_NATIVE_MODULES_SUPPORTED */ diff --git a/tests/unit-ext/module/my-custom-module.c b/tests/unit-ext/module/my-custom-module.c new file mode 100644 index 000000000..178b7db2c --- /dev/null +++ b/tests/unit-ext/module/my-custom-module.c @@ -0,0 +1,29 @@ +/* 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 "jerryscript.h" +#include "jerryscript-ext/module.h" + +#ifdef JERRYX_NATIVE_MODULES_SUPPORTED + +static jerry_value_t +my_custom_module_on_resolve (void) +{ + return jerry_create_number (42); +} /* my_custom_module_on_resolve */ + +JERRYX_NATIVE_MODULE (my_custom_module, my_custom_module_on_resolve) + +#endif /* JERRYX_NATIVE_MODULES_SUPPORTED */ diff --git a/tests/unit-ext/test-ext-module-empty.c b/tests/unit-ext/test-ext-module-empty.c new file mode 100644 index 000000000..9b108a54f --- /dev/null +++ b/tests/unit-ext/test-ext-module-empty.c @@ -0,0 +1,70 @@ +/* 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 + +#include "jerryscript.h" +#include "test-common.h" +#include "jerryscript-ext/module.h" + +int +main (int argc, char **argv) +{ + (void) argc; + (void) argv; + jerry_char_t buffer[256]; + jerry_size_t bytes_copied; + jerryx_module_resolver_t resolver = jerryx_module_native_resolver; + + jerry_init (JERRY_INIT_EMPTY); + + /* Attempt to load a non-existing module. */ + jerry_value_t module = jerryx_module_resolve ((jerry_char_t *) "some-unknown-module-name", &resolver, 1); + + TEST_ASSERT (jerry_value_has_error_flag (module)); + + /* Retrieve the error message. */ + jerry_value_t prop_name = jerry_create_string ((const jerry_char_t *) "message"); + jerry_value_t prop = jerry_get_property (module, prop_name); + + /* Assert that the error message is a string with specific contents. */ + TEST_ASSERT (jerry_value_is_string (prop)); + + bytes_copied = jerry_substring_to_utf8_char_buffer (prop, 0, 254, buffer, 256); + buffer[bytes_copied] = 0; + TEST_ASSERT (!strcmp ((const char *) buffer, "Module not found")); + + /* Release the error message property name and value. */ + jerry_release_value (prop); + jerry_release_value (prop_name); + + /* Retrieve the moduleName property. */ + prop_name = jerry_create_string ((const jerry_char_t *) "moduleName"); + prop = jerry_get_property (module, prop_name); + + /* Assert that the moduleName property is a string containing the requested module name. */ + TEST_ASSERT (jerry_value_is_string (prop)); + + bytes_copied = jerry_substring_to_utf8_char_buffer (prop, 0, 254, buffer, 256); + buffer[bytes_copied] = 0; + TEST_ASSERT (!strcmp ((const char *) buffer, "some-unknown-module-name")); + + /* Release everything. */ + jerry_release_value (prop); + jerry_release_value (prop_name); + jerry_release_value (module); + + return 0; +} /* main */ diff --git a/tools/update-webpage.sh b/tools/update-webpage.sh index 80711adab..6fef58e55 100755 --- a/tools/update-webpage.sh +++ b/tools/update-webpage.sh @@ -33,6 +33,7 @@ CODING_STANDARDS_MD="08.CODING-STANDARDS.md" EXT_REFERENCE_ARG_MD="09.EXT-REFERENCE-ARG.md" EXT_REFERENCE_HANDLER_MD="10.EXT-REFERENCE-HANDLER.md" EXT_REFERENCE_AUTORELEASE_MD="11.EXT-REFERENCE-AUTORELEASE.md" +EXT_REFERENCE_MODULE_MD="12.EXT-REFERENCE-MODULE.md" declare -A titles @@ -47,6 +48,7 @@ titles[$CODING_STANDARDS_MD]="Coding Standards" titles[$EXT_REFERENCE_ARG_MD]="'Extension API: Argument Validation'" titles[$EXT_REFERENCE_HANDLER_MD]="'Extension API: External Function Handlers'" titles[$EXT_REFERENCE_AUTORELEASE_MD]="'Extension API: Autorelease Values'" +titles[$EXT_REFERENCE_MODULE_MD]="'Extension API: Module Support'" for docfile in $docs_dir/*.md; do docfile_base=`basename $docfile`