Provide support for native modules with ES6 imports (#3090)

JerryScript-DCO-1.0-Signed-off-by: Dániel Bátyai dbatyai@inf.u-szeged.hu
This commit is contained in:
Dániel Bátyai
2019-10-02 12:20:43 +02:00
committed by GitHub
parent 2a89eec98b
commit f1883b9e7d
10 changed files with 415 additions and 201 deletions
+26 -3
View File
@@ -83,10 +83,14 @@ information.
void jerry_port_print_char (char c);
```
### ES2015 Module system helper functions
### ES2015 Module system
The module system requires two specific functions for opening and closing files.
It also requires a platform specific way of normalizing file paths.
The port API provides functions that can be used by the module system to open
and close source files, and normalize file paths.
The `jerry_port_get_native_module` port function can be used to provide native
modules to the engine. This function will be called when an import/export
statement is encountered with an unknown module specifier, which embedders can
use to supply native module objects based on the module name argument.
```c
/**
@@ -126,6 +130,25 @@ jerry_port_normalize_path (const char *in_path_p, /**< input file path */
// write to out_buf_p the normalized path
// return length of written path
} /* jerry_port_normalize_path */
/**
* Get the module object of a native module.
*
* Note:
* This port function is called by jerry-core when ES2015_MODULE_SYSTEM
* is enabled.
*
* @param name String value of the module specifier.
*
* @return Undefined, if 'name' is not a native module
* jerry_value_t containing the module object, otherwise
*/
jerry_value_t
jerry_port_get_native_module (jerry_value_t name) /**< module specifier */
{
(void) name;
return jerry_create_undefined ();
}
```
## Date
+1
View File
@@ -2,6 +2,7 @@
The module system allows users to write import and export statements in scripts, which can be used to separate the logic of the application into custom modules.
The standard's relevant part can be found [here](https://www.ecma-international.org/ecma-262/6.0/#sec-modules).
Embedders wishing to use native builtin modules with ES6 imports can use the [Port API](05.PORT-API.md#es2015-module-system) to do so.
## General
+123 -29
View File
@@ -97,12 +97,13 @@ ecma_module_create_normalized_path (const uint8_t *char_p, /**< module specifier
} /* ecma_module_create_normalized_path */
/**
* Checks if we already have a module request in the module list.
* Find a module with a specific identifier
*
* @return pointer to found or newly created module structure
* @return pointer to ecma_module_t, if found
* NULL, otherwise
*/
ecma_module_t *
ecma_module_find_or_create_module (ecma_string_t * const path_p) /**< module path */
ecma_module_find_module (ecma_string_t *const path_p) /**< module identifier */
{
ecma_module_t *current_p = JERRY_CONTEXT (ecma_modules_p);
while (current_p != NULL)
@@ -114,16 +115,59 @@ ecma_module_find_or_create_module (ecma_string_t * const path_p) /**< module pat
current_p = current_p->next_p;
}
current_p = (ecma_module_t *) jmem_heap_alloc_block (sizeof (ecma_module_t));
memset (current_p, 0, sizeof (ecma_module_t));
ecma_ref_ecma_string (path_p);
current_p->path_p = path_p;
current_p->next_p = JERRY_CONTEXT (ecma_modules_p);
JERRY_CONTEXT (ecma_modules_p) = current_p;
return current_p;
} /* ecma_module_find_module */
/**
* Create a new module
*
* @return pointer to created module
*/
static ecma_module_t *
ecma_module_create_module (ecma_string_t *const path_p) /**< module identifier */
{
ecma_module_t *module_p = (ecma_module_t *) jmem_heap_alloc_block (sizeof (ecma_module_t));
memset (module_p, 0, sizeof (ecma_module_t));
module_p->path_p = path_p;
module_p->next_p = JERRY_CONTEXT (ecma_modules_p);
JERRY_CONTEXT (ecma_modules_p) = module_p;
return module_p;
} /* ecma_module_create_module */
/**
* Checks if we already have a module request in the module list.
*
* @return pointer to found or newly created module structure
*/
ecma_module_t *
ecma_module_find_or_create_module (ecma_string_t *const path_p) /**< module path */
{
ecma_module_t *module_p = ecma_module_find_module (path_p);
if (module_p)
{
ecma_deref_ecma_string (path_p);
return module_p;
}
return ecma_module_create_module (path_p);
} /* ecma_module_find_or_create_module */
/**
* Create a new native module
*
* @return pointer to created module
*/
ecma_module_t *
ecma_module_create_native_module (ecma_string_t *const path_p, /**< module identifier */
ecma_object_t *const namespace_p) /**< module namespace */
{
ecma_module_t *module_p = ecma_module_create_module (path_p);
module_p->state = ECMA_MODULE_STATE_NATIVE;
module_p->namespace_object_p = namespace_p;
return module_p;
} /* ecma_module_create_native_module */
/**
* Creates a module context.
*
@@ -274,6 +318,30 @@ ecma_module_resolve_export (ecma_module_t * const module_p, /**< base module */
continue;
}
if (current_module_p->state == ECMA_MODULE_STATE_NATIVE)
{
ecma_object_t *object_p = current_module_p->namespace_object_p;
ecma_value_t prop_value = ecma_op_object_find_own (ecma_make_object_value (object_p),
object_p,
current_export_name_p);
if (ecma_is_value_found (prop_value))
{
found = true;
found_record.module_p = current_module_p;
found_record.name_p = current_export_name_p;
ecma_free_value (prop_value);
}
if (ecma_compare_ecma_string_to_magic_id (current_export_name_p, LIT_MAGIC_STRING_DEFAULT))
{
ret_value = ecma_raise_syntax_error (ECMA_ERR_MSG ("No default export in native module."));
break;
}
ecma_module_resolve_stack_pop (&stack_p);
continue;
}
if (context_p->local_exports_p != NULL)
{
/* 15.2.1.16.3 / 4 */
@@ -645,25 +713,44 @@ ecma_module_connect_imports (void)
return ecma_raise_syntax_error (ECMA_ERR_MSG ("Ambiguous import request."));
}
result = ecma_module_evaluate (record.module_p);
if (ECMA_IS_VALUE_ERROR (result))
if (record.module_p->state == ECMA_MODULE_STATE_NATIVE)
{
return result;
ecma_object_t *object_p = record.module_p->namespace_object_p;
ecma_value_t prop_value = ecma_op_object_find_own (ecma_make_object_value (object_p),
object_p,
record.name_p);
JERRY_ASSERT (ecma_is_value_found (prop_value));
ecma_op_create_mutable_binding (local_env_p, import_names_p->local_name_p, true /* is_deletable */);
ecma_op_set_mutable_binding (local_env_p,
import_names_p->local_name_p,
prop_value,
false /* is_strict */);
ecma_free_value (prop_value);
}
else
{
result = ecma_module_evaluate (record.module_p);
ecma_object_t *ref_base_lex_env_p;
ecma_value_t prop_value = ecma_op_get_value_lex_env_base (record.module_p->scope_p,
&ref_base_lex_env_p,
record.name_p);
if (ECMA_IS_VALUE_ERROR (result))
{
return result;
}
ecma_op_create_mutable_binding (local_env_p, import_names_p->local_name_p, true /* is_deletable */);
ecma_op_set_mutable_binding (local_env_p,
import_names_p->local_name_p,
prop_value,
false /* is_strict */);
ecma_object_t *ref_base_lex_env_p;
ecma_value_t prop_value = ecma_op_get_value_lex_env_base (record.module_p->scope_p,
&ref_base_lex_env_p,
record.name_p);
ecma_free_value (prop_value);
ecma_op_create_mutable_binding (local_env_p, import_names_p->local_name_p, true /* is_deletable */);
ecma_op_set_mutable_binding (local_env_p,
import_names_p->local_name_p,
prop_value,
false /* is_strict */);
ecma_free_value (prop_value);
}
}
import_names_p = import_names_p->next_p;
@@ -873,6 +960,17 @@ static void
ecma_module_release_module (ecma_module_t *module_p) /**< module */
{
ecma_deref_ecma_string (module_p->path_p);
if (module_p->namespace_object_p != NULL)
{
ecma_deref_object (module_p->namespace_object_p);
}
if (module_p->state == ECMA_MODULE_STATE_NATIVE)
{
goto finished;
}
if (module_p->state >= ECMA_MODULE_STATE_PARSING)
{
ecma_module_release_module_context (module_p->context_p);
@@ -889,11 +987,7 @@ ecma_module_release_module (ecma_module_t *module_p) /**< module */
ecma_bytecode_deref (module_p->compiled_code_p);
}
if (module_p->namespace_object_p != NULL)
{
ecma_deref_object (module_p->namespace_object_p);
}
finished:
jmem_heap_free_block (module_p, sizeof (ecma_module_t));
} /* ecma_module_release_module */
+9 -5
View File
@@ -70,6 +70,7 @@ typedef enum
ECMA_MODULE_STATE_PARSED = 2, /**< module has been parsed */
ECMA_MODULE_STATE_EVALUATING = 3, /**< module is currently being evaluated */
ECMA_MODULE_STATE_EVALUATED = 4, /**< module has been evaluated */
ECMA_MODULE_STATE_NATIVE = 5, /**< module is native */
} ecma_module_state_t;
/**
@@ -116,18 +117,21 @@ typedef struct ecma_module_resolve_stack
} ecma_module_resolve_stack_t;
bool ecma_module_resolve_set_insert (ecma_module_resolve_set_t **set_p,
ecma_module_t * const module_p,
ecma_string_t * const export_name_p);
ecma_module_t *const module_p,
ecma_string_t *const export_name_p);
void ecma_module_resolve_set_cleanup (ecma_module_resolve_set_t *set_p);
void ecma_module_resolve_stack_push (ecma_module_resolve_stack_t **stack_p,
ecma_module_t * const module_p,
ecma_string_t * const export_name_p);
ecma_module_t *const module_p,
ecma_string_t *const export_name_p);
void ecma_module_resolve_stack_pop (ecma_module_resolve_stack_t **stack_p);
ecma_string_t *ecma_module_create_normalized_path (const uint8_t *char_p,
prop_length_t size);
ecma_module_t *ecma_module_find_or_create_module (ecma_string_t * const path_p);
ecma_module_t *ecma_module_find_module (ecma_string_t *const path_p);
ecma_module_t *ecma_module_create_native_module (ecma_string_t *const path_p,
ecma_object_t *const namespace_p);
ecma_module_t *ecma_module_find_or_create_module (ecma_string_t *const path_p);
ecma_value_t ecma_module_connect_imports (void);
ecma_value_t ecma_module_parse_modules (void);
+15
View File
@@ -21,6 +21,7 @@
#include <stdio.h>
#include "jerryscript-compiler.h"
#include "jerryscript-core.h"
#ifdef __cplusplus
extern "C"
@@ -236,6 +237,20 @@ size_t jerry_port_normalize_path (const char *in_path_p,
size_t out_buf_size,
char *base_file_p);
/**
* Get the module object of a native module.
*
* Note:
* This port function is called by jerry-core when ES2015_MODULE_SYSTEM
* is enabled.
*
* @param name String value of the module specifier.
*
* @return Undefined, if 'name' is not a native module
* jerry_value_t containing the module object, otherwise
*/
jerry_value_t jerry_port_get_native_module (jerry_value_t name);
/**
* @}
*/
+25 -7
View File
@@ -284,16 +284,12 @@ parser_module_context_init (void)
if (path_p == NULL)
{
ecma_ref_ecma_string (path_str_p);
path_p = path_str_p;
}
ecma_module_t *module_p = ecma_module_find_or_create_module (path_p);
if (path_p != path_str_p)
{
ecma_deref_ecma_string (path_p);
}
module_p->state = ECMA_MODULE_STATE_EVALUATED;
module_p->scope_p = ecma_get_global_environment ();
ecma_ref_object (module_p->scope_p);
@@ -542,6 +538,28 @@ parser_module_handle_module_specifier (parser_context_t *context_p) /**< parser
lexer_construct_literal_object (context_p, &context_p->token.lit_location, LEXER_STRING_LITERAL);
ecma_string_t *name_p = ecma_new_ecma_string_from_utf8 (context_p->lit_object.literal_p->u.char_p,
context_p->lit_object.literal_p->prop.length);
ecma_module_t *module_p = ecma_module_find_module (name_p);
if (module_p)
{
ecma_deref_ecma_string (name_p);
goto module_found;
}
ecma_value_t native = jerry_port_get_native_module (ecma_make_string_value (name_p));
if (!ecma_is_value_undefined (native))
{
JERRY_ASSERT (ecma_is_value_object (native));
ecma_object_t *module_object_p = ecma_get_object_from_value (native);
module_p = ecma_module_create_native_module (name_p, module_object_p);
goto module_found;
}
ecma_deref_ecma_string (name_p);
ecma_string_t *path_p = ecma_module_create_normalized_path (context_p->lit_object.literal_p->u.char_p,
context_p->lit_object.literal_p->prop.length);
@@ -550,9 +568,9 @@ parser_module_handle_module_specifier (parser_context_t *context_p) /**< parser
parser_raise_error (context_p, PARSER_ERR_FILE_NOT_FOUND);
}
ecma_module_t *module_p = ecma_module_find_or_create_module (path_p);
ecma_deref_ecma_string (path_p);
module_p = ecma_module_find_or_create_module (path_p);
module_found:
module_node_p->module_request_p = module_p;
lexer_next_token (context_p);
} /* parser_module_handle_module_specifier */
-157
View File
@@ -13,10 +13,6 @@
* limitations under the License.
*/
#if !defined (WIN32)
#include <libgen.h>
#endif /* !defined (WIN32) */
#include <limits.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
@@ -132,156 +128,3 @@ jerry_port_print_char (char c) /**< the character to print */
}
#endif /* defined (JERRY_DEBUGGER) && (JERRY_DEBUGGER == 1) */
} /* jerry_port_print_char */
/**
* Determines the size of the given file.
* @return size of the file
*/
static size_t
jerry_port_get_file_size (FILE *file_p) /**< opened file */
{
fseek (file_p, 0, SEEK_END);
long size = ftell (file_p);
fseek (file_p, 0, SEEK_SET);
return (size_t) size;
} /* jerry_port_get_file_size */
/**
* Opens file with the given path and reads its source.
* @return the source of the file
*/
uint8_t *
jerry_port_read_source (const char *file_name_p, /**< file name */
size_t *out_size_p) /**< [out] read bytes */
{
FILE *file_p = fopen (file_name_p, "rb");
if (file_p == NULL)
{
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: failed to open file: %s\n", file_name_p);
return NULL;
}
size_t file_size = jerry_port_get_file_size (file_p);
uint8_t *buffer_p = (uint8_t *) malloc (file_size);
if (buffer_p == NULL)
{
fclose (file_p);
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: failed to allocate memory for module");
return NULL;
}
size_t bytes_read = fread (buffer_p, 1u, file_size, file_p);
if (!bytes_read)
{
fclose (file_p);
free (buffer_p);
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: failed to read file: %s\n", file_name_p);
return NULL;
}
fclose (file_p);
*out_size_p = bytes_read;
return buffer_p;
} /* jerry_port_read_source */
/**
* Release the previously opened file's content.
*/
void
jerry_port_release_source (uint8_t *buffer_p) /**< buffer to free */
{
free (buffer_p);
} /* jerry_port_release_source */
/**
* Normalize a file path
*
* @return length of the path written to the output buffer
*/
size_t
jerry_port_normalize_path (const char *in_path_p, /**< input file path */
char *out_buf_p, /**< output buffer */
size_t out_buf_size, /**< size of output buffer */
char *base_file_p) /**< base file path */
{
size_t ret = 0;
#if defined (WIN32)
char drive[_MAX_DRIVE];
char *dir_p = (char *) malloc (_MAX_DIR);
char *path_p = (char *) malloc (_MAX_PATH * 2);
*path_p = '\0';
if (base_file_p != NULL)
{
_splitpath_s (base_file_p,
&drive,
_MAX_DRIVE,
dir_p,
_MAX_DIR,
NULL,
0,
NULL,
0);
strncat (path_p, &drive, _MAX_DRIVE);
strncat (path_p, dir_p, _MAX_DIR);
}
strncat (path_p, in_path_p, _MAX_PATH);
char *norm_p = _fullpath (out_buf_p, path_p, out_buf_size);
free (path_p);
free (dir_p);
if (norm_p != NULL)
{
ret = strnlen (norm_p, out_buf_size);
}
#elif defined (__unix__) || defined (__APPLE__)
#define MAX_JERRY_PATH_SIZE 256
char *buffer_p = (char *) malloc (PATH_MAX);
char *path_p = (char *) malloc (PATH_MAX);
char *base_p = dirname (base_file_p);
strncpy (path_p, base_p, MAX_JERRY_PATH_SIZE);
strncat (path_p, "/", 1);
strncat (path_p, in_path_p, MAX_JERRY_PATH_SIZE);
char *norm_p = realpath (path_p, buffer_p);
free (path_p);
if (norm_p != NULL)
{
const size_t len = strnlen (norm_p, out_buf_size);
if (len < out_buf_size)
{
strncpy (out_buf_p, norm_p, out_buf_size);
ret = len;
}
}
free (buffer_p);
#undef MAX_JERRY_PATH_SIZE
#else
(void) base_file_p;
/* Do nothing, just copy the input. */
const size_t len = strnlen (in_path_p, out_buf_size);
if (len < out_buf_size)
{
strncpy (out_buf_p, in_path_p, out_buf_size);
ret = len;
}
#endif
return ret;
} /* jerry_port_normalize_path */
+192
View File
@@ -0,0 +1,192 @@
/* 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.
*/
#if !defined (WIN32)
#include <libgen.h>
#endif /* !defined (WIN32) */
#include <limits.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "jerryscript-compiler.h"
#include "jerryscript-port.h"
#include "jerryscript-port-default.h"
/**
* Determines the size of the given file.
* @return size of the file
*/
static size_t
jerry_port_get_file_size (FILE *file_p) /**< opened file */
{
fseek (file_p, 0, SEEK_END);
long size = ftell (file_p);
fseek (file_p, 0, SEEK_SET);
return (size_t) size;
} /* jerry_port_get_file_size */
/**
* Opens file with the given path and reads its source.
* @return the source of the file
*/
uint8_t *
jerry_port_read_source (const char *file_name_p, /**< file name */
size_t *out_size_p) /**< [out] read bytes */
{
FILE *file_p = fopen (file_name_p, "rb");
if (file_p == NULL)
{
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: failed to open file: %s\n", file_name_p);
return NULL;
}
size_t file_size = jerry_port_get_file_size (file_p);
uint8_t *buffer_p = (uint8_t *) malloc (file_size);
if (buffer_p == NULL)
{
fclose (file_p);
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: failed to allocate memory for module");
return NULL;
}
size_t bytes_read = fread (buffer_p, 1u, file_size, file_p);
if (!bytes_read)
{
fclose (file_p);
free (buffer_p);
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: failed to read file: %s\n", file_name_p);
return NULL;
}
fclose (file_p);
*out_size_p = bytes_read;
return buffer_p;
} /* jerry_port_read_source */
/**
* Release the previously opened file's content.
*/
void
jerry_port_release_source (uint8_t *buffer_p) /**< buffer to free */
{
free (buffer_p);
} /* jerry_port_release_source */
/**
* Normalize a file path
*
* @return length of the path written to the output buffer
*/
size_t
jerry_port_normalize_path (const char *in_path_p, /**< input file path */
char *out_buf_p, /**< output buffer */
size_t out_buf_size, /**< size of output buffer */
char *base_file_p) /**< base file path */
{
size_t ret = 0;
#if defined (WIN32)
char drive[_MAX_DRIVE];
char *dir_p = (char *) malloc (_MAX_DIR);
char *path_p = (char *) malloc (_MAX_PATH * 2);
*path_p = '\0';
if (base_file_p != NULL)
{
_splitpath_s (base_file_p,
&drive,
_MAX_DRIVE,
dir_p,
_MAX_DIR,
NULL,
0,
NULL,
0);
strncat (path_p, &drive, _MAX_DRIVE);
strncat (path_p, dir_p, _MAX_DIR);
}
strncat (path_p, in_path_p, _MAX_PATH);
char *norm_p = _fullpath (out_buf_p, path_p, out_buf_size);
free (path_p);
free (dir_p);
if (norm_p != NULL)
{
ret = strnlen (norm_p, out_buf_size);
}
#elif defined (__unix__) || defined (__APPLE__)
#define MAX_JERRY_PATH_SIZE 256
char *buffer_p = (char *) malloc (PATH_MAX);
char *path_p = (char *) malloc (PATH_MAX);
char *base_p = dirname (base_file_p);
strncpy (path_p, base_p, MAX_JERRY_PATH_SIZE);
strncat (path_p, "/", 1);
strncat (path_p, in_path_p, MAX_JERRY_PATH_SIZE);
char *norm_p = realpath (path_p, buffer_p);
free (path_p);
if (norm_p != NULL)
{
const size_t len = strnlen (norm_p, out_buf_size);
if (len < out_buf_size)
{
strncpy (out_buf_p, norm_p, out_buf_size);
ret = len;
}
}
free (buffer_p);
#undef MAX_JERRY_PATH_SIZE
#else
(void) base_file_p;
/* Do nothing, just copy the input. */
const size_t len = strnlen (in_path_p, out_buf_size);
if (len < out_buf_size)
{
strncpy (out_buf_p, in_path_p, out_buf_size);
ret = len;
}
#endif
return ret;
} /* jerry_port_normalize_path */
/**
* Get the module object of a native module.
*
* @return Undefined, if 'name' is not a native module
* jerry_value_t containing the module object, otherwise
*/
jerry_value_t
jerry_port_get_native_module (jerry_value_t name) /**< module specifier */
{
(void) name;
return jerry_create_undefined ();
} /* jerry_port_get_native_module */
+12
View File
@@ -150,6 +150,18 @@ jerry_port_normalize_path (const char *in_path_p, /**< input file path */
return len;
} /* jerry_port_normalize_path */
/**
* Get the module object of a native module.
*
* @return undefined
*/
jerry_value_t
jerry_port_get_native_module (jerry_value_t name) /**< module specifier */
{
(void) name;
return jerry_create_undefined ();
} /* jerry_port_get_native_module */
/**
* Dummy function to get the time zone adjustment.
*
@@ -593,6 +593,18 @@ jerry_port_normalize_path (const char *in_path_p, /**< input file path */
return len;
} /* jerry_port_normalize_path */
/**
* Get the module object of a native module.
*
* @return undefined
*/
jerry_value_t
jerry_port_get_native_module (jerry_value_t name) /**< module specifier */
{
(void) name;
return jerry_create_undefined ();
} /* jerry_port_get_native_module */
/**
* Main program.
*