[njs] Added initial modules support.

Dmitry Volyntsev xeioex at nginx.com
Fri Mar 22 17:49:42 UTC 2019


details:   https://hg.nginx.org/njs/rev/53dc000e8c14
branches:  
changeset: 837:53dc000e8c14
user:      hongzhidao <hongzhidao at gmail.com>
date:      Sat Mar 02 20:31:10 2019 +0800
description:
Added initial modules support.

The following syntax is supported:
1) default import statements:
	import lib1 from '../relative/path/lib.js';
	import lib2 from '/abs/path/lib.js';
	import fs from 'fs'; // built-in modules

2) default export statements:
	export default {fun1, fun2, ...}; // export module object

Modules look up procedure:
	1) absolute paths (start with '/') are used as is.
	2) otherwise the following paths are tried:
		dir + '/' + path where dir is
	  a) the directory of the current file.
	  b) additions paths provided with njs_vm_add_path().

This closes #91 on Github.

diffstat:

 njs/njs.c                             |   31 ++
 njs/njs.h                             |    2 +
 njs/njs_builtin.c                     |    2 +
 njs/njs_core.h                        |    4 +-
 njs/njs_generator.c                   |   98 ++++++-
 njs/njs_lexer.h                       |    4 +
 njs/njs_lexer_keyword.c               |    7 +-
 njs/njs_module.c                      |  455 ++++++++++++++++++++++++++++++++++
 njs/njs_module.h                      |    4 +
 njs/njs_parser.c                      |  308 ++++++++++++++++++++++-
 njs/njs_parser.h                      |    7 +
 njs/njs_shell.c                       |  102 ++++++-
 njs/njs_variable.c                    |    2 +-
 njs/njs_vm.c                          |    1 +
 njs/njs_vm.h                          |    5 +
 njs/test/module/exception.js          |    4 +
 njs/test/module/export.js             |    4 +
 njs/test/module/export_non_default.js |    3 +
 njs/test/module/lib1.js               |   19 +
 njs/test/module/lib2.js               |    7 +
 njs/test/module/lib3.js               |   11 +
 njs/test/module/libs/hash.js          |    9 +
 njs/test/module/loading_exception.js  |    3 +
 njs/test/module/normal.js             |   35 ++
 njs/test/module/recursive.js          |    3 +
 njs/test/module/return.js             |    1 +
 njs/test/module/sub/sub1.js           |   12 +
 njs/test/module/sub/sub2.js           |    7 +
 njs/test/njs_expect_test.exp          |   52 +++-
 njs/test/njs_unit_test.c              |   29 ++
 30 files changed, 1191 insertions(+), 40 deletions(-)

diffs (truncated from 1695 to 1000 lines):

diff -r 64dc0795c2f0 -r 53dc000e8c14 njs/njs.c
--- a/njs/njs.c	Wed Mar 20 15:56:22 2019 +0300
+++ b/njs/njs.c	Sat Mar 02 20:31:10 2019 +0800
@@ -332,6 +332,8 @@ njs_vm_clone(njs_vm_t *vm, njs_external_
 
         nvm->variables_hash = vm->variables_hash;
         nvm->values_hash = vm->values_hash;
+
+        nvm->modules = vm->modules;
         nvm->modules_hash = vm->modules_hash;
 
         nvm->externals_hash = vm->externals_hash;
@@ -580,6 +582,11 @@ njs_vm_start(njs_vm_t *vm)
 {
     njs_ret_t  ret;
 
+    ret = njs_module_load(vm);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
     ret = njs_vmcode_interpreter(vm);
 
     if (ret == NJS_STOP) {
@@ -628,6 +635,30 @@ njs_vm_handle_events(njs_vm_t *vm)
 }
 
 
+nxt_int_t
+njs_vm_add_path(njs_vm_t *vm, const nxt_str_t *path)
+{
+    nxt_str_t  *item;
+
+    if (vm->paths == NULL) {
+        vm->paths = nxt_array_create(4, sizeof(nxt_str_t),
+                                     &njs_array_mem_proto, vm->mem_pool);
+        if (nxt_slow_path(vm->paths == NULL)) {
+            return NXT_ERROR;
+        }
+    }
+
+    item = nxt_array_add(vm->paths, &njs_array_mem_proto, vm->mem_pool);
+    if (nxt_slow_path(item == NULL)) {
+        return NXT_ERROR;
+    }
+
+    *item = *path;
+
+    return NXT_OK;
+}
+
+
 nxt_noinline njs_value_t *
 njs_vm_retval(njs_vm_t *vm)
 {
diff -r 64dc0795c2f0 -r 53dc000e8c14 njs/njs.h
--- a/njs/njs.h	Wed Mar 20 15:56:22 2019 +0300
+++ b/njs/njs.h	Sat Mar 02 20:31:10 2019 +0800
@@ -215,6 +215,8 @@ NXT_EXPORT nxt_int_t njs_vm_run(njs_vm_t
  */
 NXT_EXPORT nxt_int_t njs_vm_start(njs_vm_t *vm);
 
+NXT_EXPORT nxt_int_t njs_vm_add_path(njs_vm_t *vm, const nxt_str_t *path);
+
 NXT_EXPORT const njs_extern_t *njs_vm_external_prototype(njs_vm_t *vm,
     njs_external_t *external);
 NXT_EXPORT nxt_int_t njs_vm_external_create(njs_vm_t *vm,
diff -r 64dc0795c2f0 -r 53dc000e8c14 njs/njs_builtin.c
--- a/njs/njs_builtin.c	Wed Mar 20 15:56:22 2019 +0300
+++ b/njs/njs_builtin.c	Sat Mar 02 20:31:10 2019 +0800
@@ -294,6 +294,8 @@ njs_builtin_objects_create(njs_vm_t *vm)
             return NJS_ERROR;
         }
 
+        module->function.native = 1;
+
         ret = njs_object_hash_create(vm, &module->object.shared_hash,
                                      obj->properties, obj->items);
         if (nxt_slow_path(ret != NXT_OK)) {
diff -r 64dc0795c2f0 -r 53dc000e8c14 njs/njs_core.h
--- a/njs/njs_core.h	Wed Mar 20 15:56:22 2019 +0300
+++ b/njs/njs_core.h	Sat Mar 02 20:31:10 2019 +0800
@@ -7,6 +7,7 @@
 #ifndef _NJS_CORE_H_INCLUDED_
 #define _NJS_CORE_H_INCLUDED_
 
+
 #include <nxt_auto_config.h>
 
 #include <nxt_unix.h>
@@ -46,7 +47,8 @@
 #include <njs_error.h>
 
 #include <njs_event.h>
+#include <njs_extern.h>
+#include <njs_module.h>
 
-#include <njs_extern.h>
 
 #endif /* _NJS_CORE_H_INCLUDED_ */
diff -r 64dc0795c2f0 -r 53dc000e8c14 njs/njs_generator.c
--- a/njs/njs_generator.c	Wed Mar 20 15:56:22 2019 +0300
+++ b/njs/njs_generator.c	Sat Mar 02 20:31:10 2019 +0800
@@ -154,6 +154,10 @@ static nxt_int_t njs_generate_try_statem
     njs_generator_t *generator, njs_parser_node_t *node);
 static nxt_int_t njs_generate_throw_statement(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
+static nxt_int_t njs_generate_import_statement(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node);
+static nxt_int_t njs_generate_export_statement(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node);
 static nxt_noinline njs_index_t njs_generate_dest_index(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
 static nxt_noinline njs_index_t
@@ -171,8 +175,9 @@ static nxt_noinline nxt_int_t njs_genera
 static nxt_noinline nxt_int_t njs_generate_index_release(njs_vm_t *vm,
     njs_generator_t *generator, njs_index_t index);
 
-static nxt_int_t njs_generate_function_debug(njs_vm_t *vm, nxt_str_t *name,
-    njs_function_lambda_t *lambda, njs_parser_node_t *node);
+static nxt_int_t njs_generate_function_debug(njs_vm_t *vm,
+    const nxt_str_t *name, njs_function_lambda_t *lambda,
+    njs_parser_node_t *node);
 
 
 #define njs_generate_code(generator, type, _code, _operation, nargs, _retval) \
@@ -467,6 +472,12 @@ njs_generator(njs_vm_t *vm, njs_generato
     case NJS_TOKEN_THROW:
         return njs_generate_throw_statement(vm, generator, node);
 
+    case NJS_TOKEN_IMPORT:
+        return njs_generate_import_statement(vm, generator, node);
+
+    case NJS_TOKEN_EXPORT:
+        return njs_generate_export_statement(vm, generator, node);
+
     default:
         nxt_thread_log_debug("unknown token: %d", node->token);
         njs_internal_error(vm, "Generator failed: unknown token");
@@ -1909,19 +1920,25 @@ njs_generate_function(njs_vm_t *vm, njs_
     njs_parser_node_t *node)
 {
     nxt_int_t              ret;
+    nxt_bool_t             module;
+    const nxt_str_t        *name;
     njs_function_lambda_t  *lambda;
     njs_vmcode_function_t  *function;
 
     lambda = node->u.value.data.u.lambda;
-
-    ret = njs_generate_function_scope(vm, lambda, node, &njs_entry_anonymous);
+    module = node->right->scope->module;
+
+    name = module ? &njs_entry_module : &njs_entry_anonymous;
+
+    ret = njs_generate_function_scope(vm, lambda, node, name);
 
     if (nxt_slow_path(ret != NXT_OK)) {
         return ret;
     }
 
     if (vm->debug != NULL) {
-        ret = njs_generate_function_debug(vm, NULL, lambda, node);
+        ret = njs_generate_function_debug(vm, name, lambda,
+                                          module ? node->right : node);
         if (nxt_slow_path(ret != NXT_OK)) {
             return ret;
         }
@@ -2450,7 +2467,6 @@ njs_generate_return_statement(njs_vm_t *
     njs_vmcode_try_return_t  *try_return;
 
     ret = njs_generator(vm, generator, node->right);
-
     if (nxt_slow_path(ret != NXT_OK)) {
         return ret;
     }
@@ -3012,6 +3028,66 @@ njs_generate_throw_statement(njs_vm_t *v
 }
 
 
+static nxt_int_t
+njs_generate_import_statement(njs_vm_t *vm, njs_generator_t *generator,
+    njs_parser_node_t *node)
+{
+    nxt_int_t                 ret;
+    njs_index_t               index;
+    njs_module_t              *module;
+    njs_parser_node_t         *lvalue, *expr;
+    njs_vmcode_object_copy_t  *copy;
+
+    lvalue = node->left;
+    expr = node->right;
+
+    index = njs_variable_index(vm, lvalue);
+    if (nxt_slow_path(index == NJS_INDEX_ERROR)) {
+        return NXT_ERROR;
+    }
+
+    if (expr->left != NULL) {
+        ret = njs_generator(vm, generator, expr->left);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+    }
+
+    module = (njs_module_t *) expr->index;
+
+    njs_generate_code(generator, njs_vmcode_object_copy_t, copy,
+                      njs_vmcode_object_copy, 2, 1);
+    copy->retval = index;
+    copy->object = module->index;
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_generate_export_statement(njs_vm_t *vm, njs_generator_t *generator,
+    njs_parser_node_t *node)
+{
+    nxt_int_t            ret;
+    njs_parser_node_t    *obj;
+    njs_vmcode_return_t  *code;
+
+    obj = node->right;
+
+    ret = njs_generator(vm, generator, obj);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    njs_generate_code(generator, njs_vmcode_return_t, code,
+                      njs_vmcode_return, 1, 0);
+    code->retval = obj->index;
+    node->index = obj->index;
+
+    return NXT_OK;
+}
+
+
 static nxt_noinline njs_index_t
 njs_generate_dest_index(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
@@ -3162,7 +3238,7 @@ njs_generate_index_release(njs_vm_t *vm,
 
 
 static nxt_int_t
-njs_generate_function_debug(njs_vm_t *vm, nxt_str_t *name,
+njs_generate_function_debug(njs_vm_t *vm, const nxt_str_t *name,
     njs_function_lambda_t *lambda, njs_parser_node_t *node)
 {
     njs_function_debug_t  *debug;
@@ -3172,16 +3248,10 @@ njs_generate_function_debug(njs_vm_t *vm
         return NXT_ERROR;
     }
 
-    if (name != NULL) {
-        debug->name = *name;
-
-    } else {
-        debug->name = no_label;
-    }
-
     debug->lambda = lambda;
     debug->line = node->token_line;
     debug->file = node->scope->file;
+    debug->name = (name != NULL) ? *name : no_label;
 
     return NXT_OK;
 }
diff -r 64dc0795c2f0 -r 53dc000e8c14 njs/njs_lexer.h
--- a/njs/njs_lexer.h	Wed Mar 20 15:56:22 2019 +0300
+++ b/njs/njs_lexer.h	Sat Mar 02 20:31:10 2019 +0800
@@ -203,6 +203,10 @@ typedef enum {
     NJS_TOKEN_SET_IMMEDIATE,
     NJS_TOKEN_CLEAR_TIMEOUT,
 
+    NJS_TOKEN_IMPORT,
+    NJS_TOKEN_FROM,
+    NJS_TOKEN_EXPORT,
+
     NJS_TOKEN_RESERVED,
 } njs_token_t;
 
diff -r 64dc0795c2f0 -r 53dc000e8c14 njs/njs_lexer_keyword.c
--- a/njs/njs_lexer_keyword.c	Wed Mar 20 15:56:22 2019 +0300
+++ b/njs/njs_lexer_keyword.c	Sat Mar 02 20:31:10 2019 +0800
@@ -93,6 +93,11 @@ static const njs_keyword_t  njs_keywords
     { nxt_string("setImmediate"),  NJS_TOKEN_SET_IMMEDIATE, 0 },
     { nxt_string("clearTimeout"),  NJS_TOKEN_CLEAR_TIMEOUT, 0 },
 
+    /* Module. */
+    { nxt_string("import"),        NJS_TOKEN_IMPORT, 0 },
+    { nxt_string("from"),          NJS_TOKEN_FROM, 0 },
+    { nxt_string("export"),        NJS_TOKEN_EXPORT, 0 },
+
     /* Reserved words. */
 
     { nxt_string("await"),         NJS_TOKEN_RESERVED, 0 },
@@ -100,10 +105,8 @@ static const njs_keyword_t  njs_keywords
     { nxt_string("const"),         NJS_TOKEN_RESERVED, 0 },
     { nxt_string("debugger"),      NJS_TOKEN_RESERVED, 0 },
     { nxt_string("enum"),          NJS_TOKEN_RESERVED, 0 },
-    { nxt_string("export"),        NJS_TOKEN_RESERVED, 0 },
     { nxt_string("extends"),       NJS_TOKEN_RESERVED, 0 },
     { nxt_string("implements"),    NJS_TOKEN_RESERVED, 0 },
-    { nxt_string("import"),        NJS_TOKEN_RESERVED, 0 },
     { nxt_string("interface"),     NJS_TOKEN_RESERVED, 0 },
     { nxt_string("let"),           NJS_TOKEN_RESERVED, 0 },
     { nxt_string("package"),       NJS_TOKEN_RESERVED, 0 },
diff -r 64dc0795c2f0 -r 53dc000e8c14 njs/njs_module.c
--- a/njs/njs_module.c	Wed Mar 20 15:56:22 2019 +0300
+++ b/njs/njs_module.c	Sat Mar 02 20:31:10 2019 +0800
@@ -8,6 +8,369 @@
 #include <njs_module.h>
 #include <string.h>
 #include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+
+
+typedef struct {
+    int                 fd;
+    nxt_str_t           name;
+    nxt_str_t           file;
+} njs_module_info_t;
+
+
+static nxt_int_t njs_module_lookup(njs_vm_t *vm, const nxt_str_t *cwd,
+    njs_module_info_t *info);
+static nxt_noinline nxt_int_t njs_module_relative_path(njs_vm_t *vm,
+    const nxt_str_t *dir, njs_module_info_t *info);
+static nxt_int_t njs_module_absolute_path(njs_vm_t *vm,
+    njs_module_info_t *info);
+static nxt_bool_t njs_module_realpath_equal(const nxt_str_t *path1,
+    const nxt_str_t *path2);
+static nxt_int_t njs_module_read(njs_vm_t *vm, int fd, nxt_str_t *body);
+static njs_module_t *njs_module_find(njs_vm_t *vm, nxt_str_t *name);
+static njs_module_t *njs_module_add(njs_vm_t *vm, nxt_str_t *name);
+static nxt_int_t njs_module_insert(njs_vm_t *vm, njs_module_t *module);
+
+
+nxt_int_t
+njs_module_load(njs_vm_t *vm)
+{
+    nxt_int_t     ret;
+    nxt_uint_t    i;
+    njs_value_t   *value;
+    njs_module_t  **item, *module;
+
+    if (vm->modules == NULL) {
+        return NXT_OK;
+    }
+
+    item = vm->modules->start;
+
+    for (i = 0; i < vm->modules->items; i++) {
+        module = *item;
+
+        if (module->function.native) {
+            value = njs_vmcode_operand(vm, module->index);
+            value->data.u.object = &module->object;
+            value->type = NJS_OBJECT;
+            value->data.truth = 1;
+
+        } else {
+            ret = njs_vm_invoke(vm, &module->function, NULL, 0, module->index);
+            if (ret == NXT_ERROR) {
+                goto done;
+            }
+        }
+
+        item++;
+    }
+
+    ret = NXT_OK;
+
+done:
+
+    if (vm->options.accumulative) {
+        nxt_array_reset(vm->modules);
+    }
+
+    return ret;
+}
+
+
+nxt_int_t
+njs_parser_module(njs_vm_t *vm, njs_parser_t *parser)
+{
+    nxt_int_t          ret;
+    nxt_str_t          name, text;
+    njs_lexer_t        *prev, lexer;
+    njs_token_t        token;
+    njs_module_t       *module;
+    njs_parser_node_t  *node;
+    njs_module_info_t  info;
+
+    name = *njs_parser_text(parser);
+
+    parser->node = NULL;
+
+    module = njs_module_find(vm, &name);
+    if (module != NULL) {
+        goto found;
+    }
+
+    prev = parser->lexer;
+
+    nxt_memzero(&text, sizeof(nxt_str_t));
+
+    if (vm->options.sandbox || name.length == 0) {
+        njs_parser_syntax_error(vm, parser, "Cannot find module \"%V\"", &name);
+        goto fail;
+    }
+
+    /* Non-native module. */
+
+    nxt_memzero(&info, sizeof(njs_module_info_t));
+
+    info.name = name;
+
+    ret = njs_module_lookup(vm, &parser->scope->cwd, &info);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        njs_parser_syntax_error(vm, parser, "Cannot find module \"%V\"", &name);
+        goto fail;
+    }
+
+    ret = njs_module_read(vm, info.fd, &text);
+
+    close(info.fd);
+
+    if (nxt_slow_path(ret != NXT_OK)) {
+        njs_internal_error(vm, "while reading \"%V\" module", &name);
+        goto fail;
+    }
+
+    if (njs_module_realpath_equal(&prev->file, &info.file)) {
+        njs_parser_syntax_error(vm, parser, "Cannot import itself \"%V\"",
+                                &name);
+        goto fail;
+    }
+
+    ret = njs_lexer_init(vm, &lexer, &info.file, text.start,
+                         text.start + text.length);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return NJS_ERROR;
+    }
+
+    parser->lexer = &lexer;
+
+    token = njs_parser_token(vm, parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        goto fail;
+    }
+
+    token = njs_parser_module_lambda(vm, parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        goto fail;
+    }
+
+    module = njs_module_add(vm, &name);
+    if (nxt_slow_path(module == NULL)) {
+        goto fail;
+    }
+
+    module->function.u.lambda = parser->node->u.value.data.u.lambda;
+
+    nxt_mp_free(vm->mem_pool, text.start);
+
+    parser->lexer = prev;
+
+found:
+
+    node = njs_parser_node_new(vm, parser, 0);
+    if (nxt_slow_path(node == NULL)) {
+       return NXT_ERROR;
+    }
+
+    node->left = parser->node;
+
+    if (module->index == 0) {
+        ret = njs_module_insert(vm, module);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return NXT_ERROR;
+        }
+    }
+
+    node->index = (njs_index_t) module;
+
+    parser->node = node;
+
+    return NXT_OK;
+
+fail:
+
+    parser->lexer = prev;
+
+    if (text.start != NULL) {
+        nxt_mp_free(vm->mem_pool, text.start);
+    }
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+njs_module_lookup(njs_vm_t *vm, const nxt_str_t *cwd, njs_module_info_t *info)
+{
+    nxt_int_t   ret;
+    nxt_str_t   *path;
+    nxt_uint_t  i;
+
+    if (info->name.start[0] == '/') {
+        return njs_module_absolute_path(vm, info);
+    }
+
+    ret = njs_module_relative_path(vm, cwd, info);
+    if (ret == NXT_OK) {
+        return ret;
+    }
+
+    if (vm->paths == NULL) {
+        return NXT_DECLINED;
+    }
+
+    path = vm->paths->start;
+
+    for (i = 0; i < vm->paths->items; i++) {
+        ret = njs_module_relative_path(vm, path, info);
+        if (ret == NXT_OK) {
+            return ret;
+        }
+
+        path++;
+    }
+
+    return NXT_DECLINED;
+}
+
+
+static nxt_int_t
+njs_module_absolute_path(njs_vm_t *vm, njs_module_info_t *info)
+{
+    nxt_str_t  file;
+
+    file.length = info->name.length;
+    file.start = nxt_mp_alloc(vm->mem_pool, file.length + 1);
+    if (nxt_slow_path(file.start == NULL)) {
+        return NXT_ERROR;
+    }
+
+    memcpy(file.start, info->name.start, file.length);
+    file.start[file.length] = '\0';
+
+    info->fd = open((char *) file.start, O_RDONLY);
+    if (info->fd < 0) {
+        nxt_mp_free(vm->mem_pool, file.start);
+        return NXT_DECLINED;
+    }
+
+    info->file = file;
+
+    return NXT_OK;
+}
+
+
+static nxt_noinline nxt_int_t
+njs_module_relative_path(njs_vm_t *vm, const nxt_str_t *dir,
+    njs_module_info_t *info)
+{
+    u_char      *p;
+    nxt_str_t   file;
+    nxt_bool_t  trail;
+
+    file.length = dir->length;
+
+    trail = (dir->start[dir->length - 1] != '/');
+
+    if (trail) {
+        file.length++;
+    }
+
+    file.length += info->name.length;
+
+    file.start = nxt_mp_alloc(vm->mem_pool, file.length + 1);
+    if (nxt_slow_path(file.start == NULL)) {
+        return NXT_ERROR;
+    }
+
+    p = nxt_cpymem(file.start, dir->start, dir->length);
+
+    if (trail) {
+        *p++ = '/';
+    }
+
+    p = nxt_cpymem(p, info->name.start, info->name.length);
+    *p = '\0';
+
+    info->fd = open((char *) file.start, O_RDONLY);
+    if (info->fd < 0) {
+        nxt_mp_free(vm->mem_pool, file.start);
+        return NXT_DECLINED;
+    }
+
+    info->file = file;
+
+    return NXT_OK;
+}
+
+
+#define NJS_MODULE_START   "function() {"
+#define NJS_MODULE_END     "}"
+
+static nxt_int_t
+njs_module_read(njs_vm_t *vm, int fd, nxt_str_t *text)
+{
+    u_char       *p;
+    ssize_t      n;
+    struct stat  sb;
+
+    if (fstat(fd, &sb) == -1) {
+        goto fail;
+    }
+
+    text->length = nxt_length(NJS_MODULE_START);
+
+    if (S_ISREG(sb.st_mode) && sb.st_size) {
+        text->length += sb.st_size;
+    }
+
+    text->length += nxt_length(NJS_MODULE_END);
+
+    text->start = nxt_mp_alloc(vm->mem_pool, text->length);
+    if (text->start == NULL) {
+        goto fail;
+    }
+
+    p = nxt_cpymem(text->start, NJS_MODULE_START, nxt_length(NJS_MODULE_START));
+
+    n = read(fd, p, sb.st_size);
+
+    if (n < 0) {
+        goto fail;
+    }
+
+    if (n != sb.st_size) {
+        goto fail;
+    }
+
+    p += n;
+
+    memcpy(p, NJS_MODULE_END, nxt_length(NJS_MODULE_END));
+
+    return NXT_OK;
+
+fail:
+
+    if (text->start != NULL) {
+        nxt_mp_free(vm->mem_pool, text->start);
+    }
+
+    return NXT_ERROR;
+}
+
+
+static nxt_bool_t
+njs_module_realpath_equal(const nxt_str_t *path1, const nxt_str_t *path2)
+{
+    char  rpath1[MAXPATHLEN], rpath2[MAXPATHLEN];
+
+    realpath((char *) path1->start, rpath1);
+    realpath((char *) path2->start, rpath2);
+
+    return (strcmp(rpath1, rpath2) == 0);
+}
 
 
 static nxt_int_t
@@ -36,6 +399,98 @@ const nxt_lvlhsh_proto_t  njs_modules_ha
 };
 
 
+static njs_module_t *
+njs_module_find(njs_vm_t *vm, nxt_str_t *name)
+{
+    nxt_lvlhsh_query_t  lhq;
+
+    lhq.key = *name;
+    lhq.key_hash = nxt_djb_hash(name->start, name->length);
+    lhq.proto = &njs_modules_hash_proto;
+
+    if (nxt_lvlhsh_find(&vm->modules_hash, &lhq) == NXT_OK) {
+        return lhq.value;
+    }
+
+    return NULL;
+}
+
+
+static njs_module_t *
+njs_module_add(njs_vm_t *vm, nxt_str_t *name)
+{
+    nxt_int_t           ret;
+    njs_module_t        *module;
+    nxt_lvlhsh_query_t  lhq;
+
+    module = nxt_mp_zalloc(vm->mem_pool, sizeof(njs_module_t));
+    if (nxt_slow_path(module == NULL)) {
+        njs_memory_error(vm);
+        return NULL;
+    }
+
+    ret = njs_name_copy(vm, &module->name, name);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        nxt_mp_free(vm->mem_pool, module);
+        njs_memory_error(vm);
+        return NULL;
+    }
+
+    lhq.replace = 0;
+    lhq.key = *name;
+    lhq.key_hash = nxt_djb_hash(name->start, name->length);
+    lhq.value = module;
+    lhq.pool = vm->mem_pool;
+    lhq.proto = &njs_modules_hash_proto;
+
+    ret = nxt_lvlhsh_insert(&vm->modules_hash, &lhq);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+        return module;
+    }
+
+    nxt_mp_free(vm->mem_pool, module->name.start);
+    nxt_mp_free(vm->mem_pool, module);
+
+    njs_internal_error(vm, "lvlhsh insert failed");
+
+    return NULL;
+}
+
+
+static nxt_int_t
+njs_module_insert(njs_vm_t *vm, njs_module_t *module)
+{
+    njs_module_t        **value;
+    njs_parser_scope_t  *scope;
+
+    scope = njs_parser_global_scope(vm);
+
+    module->index = njs_scope_next_index(vm, scope, NJS_SCOPE_INDEX_LOCAL,
+                                         &njs_value_undefined);
+    if (nxt_slow_path(module->index == NJS_INDEX_ERROR)) {
+        return NXT_ERROR;
+    }
+
+    if (vm->modules == NULL) {
+        vm->modules = nxt_array_create(4, sizeof(njs_module_t *),
+                                       &njs_array_mem_proto, vm->mem_pool);
+        if (nxt_slow_path(vm->modules == NULL)) {
+            return NXT_ERROR;
+        }
+    }
+
+    value = nxt_array_add(vm->modules, &njs_array_mem_proto, vm->mem_pool);
+    if (nxt_slow_path(value == NULL)) {
+        return NXT_ERROR;
+    }
+
+    *value = module;
+
+    return NXT_OK;
+}
+
+
 njs_ret_t njs_module_require(njs_vm_t *vm, njs_value_t *args,
     nxt_uint_t nargs, njs_index_t unused)
 {
diff -r 64dc0795c2f0 -r 53dc000e8c14 njs/njs_module.h
--- a/njs/njs_module.h	Wed Mar 20 15:56:22 2019 +0300
+++ b/njs/njs_module.h	Sat Mar 02 20:31:10 2019 +0800
@@ -11,9 +11,13 @@
 typedef struct {
     nxt_str_t                   name;
     njs_object_t                object;
+    njs_index_t                 index;
+    njs_function_t              function;
 } njs_module_t;
 
 
+nxt_int_t njs_module_load(njs_vm_t *vm);
+nxt_int_t njs_parser_module(njs_vm_t *vm, njs_parser_t *parser);
 njs_ret_t njs_module_require(njs_vm_t *vm, njs_value_t *args,
     nxt_uint_t nargs, njs_index_t unused);
 
diff -r 64dc0795c2f0 -r 53dc000e8c14 njs/njs_parser.c
--- a/njs/njs_parser.c	Wed Mar 20 15:56:22 2019 +0300
+++ b/njs/njs_parser.c	Sat Mar 02 20:31:10 2019 +0800
@@ -59,6 +59,13 @@ static njs_token_t njs_parser_try_statem
 static njs_token_t njs_parser_try_block(njs_vm_t *vm, njs_parser_t *parser);
 static njs_token_t njs_parser_throw_statement(njs_vm_t *vm,
     njs_parser_t *parser);
+static njs_token_t njs_parser_import_statement(njs_vm_t *vm,
+    njs_parser_t *parser);
+static njs_token_t njs_parser_export_statement(njs_vm_t *vm,
+    njs_parser_t *parser);
+static nxt_int_t njs_parser_import_hoist(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *new_node);
+static nxt_int_t njs_parser_export_sink(njs_vm_t *vm, njs_parser_t *parser);
 static njs_token_t njs_parser_grouping_expression(njs_vm_t *vm,
     njs_parser_t *parser);
 static njs_parser_node_t *njs_parser_reference(njs_vm_t *vm,
@@ -87,9 +94,6 @@ static njs_token_t njs_parser_unexpected
 #define njs_parser_chain_top_set(parser, node)                      \
     (parser)->scope->top = node
 
-#define njs_parser_text(parser)                                     \
-    &(parser)->lexer->lexer_token->text
-
 #define njs_parser_key_hash(parser)                                 \
     (parser)->lexer->lexer_token->key_hash
 
@@ -259,6 +263,7 @@ njs_parser_scope_begin(njs_vm_t *vm, njs
 
     if (lexer->file.length != 0) {
         nxt_file_basename(&lexer->file, &scope->file);
+        nxt_file_dirname(&lexer->file, &scope->cwd);
     }
 
     parent = parser->scope;
@@ -394,6 +399,14 @@ njs_parser_statement(njs_vm_t *vm, njs_p
             token = njs_parser_brk_statement(vm, parser, token);
             break;
 
+        case NJS_TOKEN_IMPORT:
+            token = njs_parser_import_statement(vm, parser);
+            break;
+
+        case NJS_TOKEN_EXPORT:
+            token = njs_parser_export_statement(vm, parser);
+            break;
+
         case NJS_TOKEN_NAME:
             offset = 0;
             if (njs_parser_peek_token(vm, parser, &offset) == NJS_TOKEN_COLON) {
@@ -955,9 +968,13 @@ njs_parser_return_statement(njs_vm_t *vm
     njs_parser_scope_t  *scope;
 
     for (scope = parser->scope;
-         scope->type != NJS_SCOPE_FUNCTION;
+         scope != NULL;
          scope = scope->parent)
     {
+        if (scope->type == NJS_SCOPE_FUNCTION && !scope->module) {
+            break;
+        }
+
         if (scope->type == NJS_SCOPE_GLOBAL) {
             njs_parser_syntax_error(vm, parser, "Illegal return statement");
 
@@ -1878,6 +1895,289 @@ njs_parser_throw_statement(njs_vm_t *vm,
 
 
 static njs_token_t
+njs_parser_import_statement(njs_vm_t *vm, njs_parser_t *parser)
+{
+    njs_ret_t          ret;
+    njs_token_t        token;
+    njs_variable_t     *var;
+    njs_parser_node_t  *name, *import;
+
+    if (parser->scope->type != NJS_SCOPE_GLOBAL
+        && !parser->scope->module)
+    {
+        njs_parser_syntax_error(vm, parser, "Illegal import statement");
+
+        return NJS_TOKEN_ERROR;
+    }
+
+    token = njs_parser_token(vm, parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    if (token != NJS_TOKEN_NAME) {
+        njs_parser_syntax_error(vm, parser,
+                                "Non-default import is not supported");
+        return NJS_TOKEN_ILLEGAL;
+    }
+
+    var = njs_parser_variable_add(vm, parser, NJS_VARIABLE_VAR);
+    if (nxt_slow_path(var == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    name = njs_parser_node_new(vm, parser, NJS_TOKEN_NAME);
+    if (nxt_slow_path(name == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    ret = njs_parser_variable_reference(vm, parser, name, NJS_DECLARATION);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    token = njs_parser_token(vm, parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    token = njs_parser_match(vm, parser, token, NJS_TOKEN_FROM);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    if (token != NJS_TOKEN_STRING) {
+        return NJS_TOKEN_ILLEGAL;
+    }
+
+    ret = njs_parser_module(vm, parser);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    import = njs_parser_node_new(vm, parser, NJS_TOKEN_IMPORT);
+    if (nxt_slow_path(import == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    import->left = name;
+    import->right = parser->node;
+
+    ret = njs_parser_import_hoist(vm, parser, import);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    parser->node = NULL;
+
+    return njs_parser_token(vm, parser);
+}
+
+
+njs_token_t
+njs_parser_module_lambda(njs_vm_t *vm, njs_parser_t *parser)
+{
+    njs_ret_t              ret;
+    njs_token_t            token;
+    njs_parser_node_t      *node, *parent;
+    njs_function_lambda_t  *lambda;
+
+    node = njs_parser_node_new(vm, parser, NJS_TOKEN_FUNCTION_EXPRESSION);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node->token_line = njs_parser_token_line(parser);
+
+    token = njs_parser_token(vm, parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    lambda = nxt_mp_zalloc(vm->mem_pool, sizeof(njs_function_lambda_t));
+    if (nxt_slow_path(lambda == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node->u.value.data.u.lambda = lambda;
+    parser->node = node;
+
+    ret = njs_parser_scope_begin(vm, parser, NJS_SCOPE_FUNCTION);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    parser->scope->module = 1;
+
+    token = njs_parser_match(vm, parser, token, NJS_TOKEN_OPEN_PARENTHESIS);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    parent = parser->node;
+
+    token = njs_parser_match(vm, parser, token, NJS_TOKEN_CLOSE_PARENTHESIS);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    token = njs_parser_lambda_statements(vm, parser, token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    ret = njs_parser_export_sink(vm, parser);


More information about the nginx-devel mailing list