]> git.kaiwu.me - njs.git/commitdiff
Added initial modules support.
authorhongzhidao <hongzhidao@gmail.com>
Sat, 2 Mar 2019 12:31:10 +0000 (20:31 +0800)
committerhongzhidao <hongzhidao@gmail.com>
Sat, 2 Mar 2019 12:31:10 +0000 (20:31 +0800)
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.

31 files changed:
njs/njs.c
njs/njs.h
njs/njs_builtin.c
njs/njs_core.h
njs/njs_generator.c
njs/njs_lexer.h
njs/njs_lexer_keyword.c
njs/njs_module.c
njs/njs_module.h
njs/njs_parser.c
njs/njs_parser.h
njs/njs_shell.c
njs/njs_variable.c
njs/njs_vm.c
njs/njs_vm.h
njs/test/module/empty.js [new file with mode: 0644]
njs/test/module/exception.js [new file with mode: 0644]
njs/test/module/export.js [new file with mode: 0644]
njs/test/module/export_non_default.js [new file with mode: 0644]
njs/test/module/lib1.js [new file with mode: 0644]
njs/test/module/lib2.js [new file with mode: 0644]
njs/test/module/lib3.js [new file with mode: 0644]
njs/test/module/libs/hash.js [new file with mode: 0644]
njs/test/module/loading_exception.js [new file with mode: 0644]
njs/test/module/normal.js [new file with mode: 0644]
njs/test/module/recursive.js [new file with mode: 0644]
njs/test/module/return.js [new file with mode: 0644]
njs/test/module/sub/sub1.js [new file with mode: 0644]
njs/test/module/sub/sub2.js [new file with mode: 0644]
njs/test/njs_expect_test.exp
njs/test/njs_unit_test.c

index acbd83dcab11ba6be8635a7721ebc915ffaab05e..fa1e560fe361be3e76a3673518ce83e09585221b 100644 (file)
--- a/njs/njs.c
+++ b/njs/njs.c
@@ -332,6 +332,8 @@ njs_vm_clone(njs_vm_t *vm, njs_external_ptr_t 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)
 {
index 631cbd61ff76c1d858581653a5d05adeae7787ba..147734244eb9a06a9624931c14ef9b76bf4cdf47 100644 (file)
--- a/njs/njs.h
+++ b/njs/njs.h
@@ -215,6 +215,8 @@ NXT_EXPORT nxt_int_t njs_vm_run(njs_vm_t *vm);
  */
 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,
index 7976eabffdfa88d63002341e4d4d840db561f628..985e5f4893584580f46aa7594847ea7b0c7f1057 100644 (file)
@@ -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)) {
index 9b25d76cd94853c374ab3c15145c8494f1aadf04..f8ec3d23d68a89c2597be02fa4a57193648f6dce 100644 (file)
@@ -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>
+
 
 #endif /* _NJS_CORE_H_INCLUDED_ */
index b2ae85e06406e9f3f7cd971becf68418abefefcb..bb0a0cc2afcd1c338eb6607e7795c33b1b182ec6 100644 (file)
@@ -154,6 +154,10 @@ static nxt_int_t njs_generate_try_statement(njs_vm_t *vm,
     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_generate_node_index_release(njs_vm_t *vm,
 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_generator_t *generator, njs_parser_node_t *node)
     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_generator_t *generator,
     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;
+    module = node->right->scope->module;
+
+    name = module ? &njs_entry_module : &njs_entry_anonymous;
 
-    ret = njs_generate_function_scope(vm, lambda, node, &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 *vm, njs_generator_t *generator,
     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 *vm, njs_generator_t *generator,
 }
 
 
+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, njs_generator_t *generator,
 
 
 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, nxt_str_t *name,
         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;
 }
index caae37b2891e6e1f2ac911a432f454935ea7164f..62712ac8a08d70e2faebb549470ab245a4af8223 100644 (file)
@@ -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;
 
index bd7ddb92633978e0b7f8f1dc365948108258dd71..f1d6d0716b8f684fa3715b63eb7a57edee0729c5 100644 (file)
@@ -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 },
index 5aa974d3b662a3829afef4782ba242357bafc71c..decb27410e5dca2058d64ce136923f407a79cf00 100644 (file)
@@ -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_hash_proto
 };
 
 
+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)
 {
index 697d2a1c241f4370abe3c9ab8d15082614f8e44a..63e86b9a9ba046ea51c7028abdb7b32dd6d81abc 100644 (file)
 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);
 
index 9329acd3e62bb90d47df04ba9554579254f1fac1..9b6b6a7ba2cc64296fba882c547d51da73f7ce55 100644 (file)
@@ -59,6 +59,13 @@ static njs_token_t njs_parser_try_statement(njs_vm_t *vm, njs_parser_t *parser);
 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_token(njs_vm_t *vm,
 #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_parser_t *parser, njs_scope_t type)
 
     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_parser_t *parser,
             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_t *parser)
     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");
 
@@ -1877,6 +1894,289 @@ 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)
+{
+    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);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    parent->right = njs_parser_chain_top(parser);
+    parent->right->token_line = 1;
+
+    parser->node = parent;
+
+    njs_parser_scope_end(vm, parser);
+
+    return token;
+}
+
+
+static njs_token_t
+njs_parser_export_statement(njs_vm_t *vm, njs_parser_t *parser)
+{
+    njs_token_t        token;
+    njs_parser_node_t  *node;
+
+    if (!parser->scope->module) {
+        njs_parser_syntax_error(vm, parser, "Illegal export statement");
+        return NXT_ERROR;
+    }
+
+    node = njs_parser_node_new(vm, parser, NJS_TOKEN_EXPORT);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    parser->node = node;
+
+    token = njs_parser_token(vm, parser);
+    if (nxt_slow_path(token != NJS_TOKEN_DEFAULT)) {
+        njs_parser_syntax_error(vm, parser,
+                                "Non-default export is not supported");
+        return NJS_TOKEN_ILLEGAL;
+    }
+
+    token = njs_parser_token(vm, parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    token = njs_parser_expression(vm, parser, token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    if (parser->node->token != NJS_TOKEN_OBJECT) {
+        njs_parser_syntax_error(vm, parser, "Illegal export value");
+        return NXT_ERROR;
+    }
+
+    node->right = parser->node;
+    parser->node = node;
+
+    return token;
+}
+
+
+static nxt_int_t
+njs_parser_import_hoist(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *new_node)
+{
+    njs_parser_node_t  *node, *stmt, **child;
+
+    child = &njs_parser_chain_top(parser);
+
+    while (*child != NULL) {
+        node = *child;
+
+        if (node->right != NULL
+            && node->right->token == NJS_TOKEN_IMPORT)
+        {
+            break;
+        }
+
+        child = &node->left;
+    }
+
+    stmt = njs_parser_node_new(vm, parser, NJS_TOKEN_STATEMENT);
+    if (nxt_slow_path(stmt == NULL)) {
+        return NXT_ERROR;
+    }
+
+    stmt->left = *child;
+    stmt->right = new_node;
+
+    *child = stmt;
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_parser_export_sink(njs_vm_t *vm, njs_parser_t *parser)
+{
+    nxt_uint_t         n;
+    njs_parser_node_t  *node, *prev;
+
+    n = 0;
+
+    for (node = njs_parser_chain_top(parser);
+         node != NULL;
+         node = node->left)
+    {
+        if (node->right != NULL
+            && node->right->token == NJS_TOKEN_EXPORT)
+        {
+            n++;
+        }
+    }
+
+    if (n != 1) {
+        njs_parser_syntax_error(vm, parser,
+             (n == 0) ? "export statement is required"
+                      : "Identifier \"default\" has already been declared");
+        return NXT_ERROR;
+    }
+
+    node = njs_parser_chain_top(parser);
+
+    if (node->right && node->right->token == NJS_TOKEN_EXPORT) {
+        return NXT_OK;
+    }
+
+    prev = njs_parser_chain_top(parser);
+
+    while (prev->left != NULL) {
+        node = prev->left;
+
+        if (node->right != NULL
+            && node->right->token == NJS_TOKEN_EXPORT)
+        {
+            prev->left = node->left;
+            break;
+        }
+
+        prev = prev->left;
+    }
+
+    node->left = njs_parser_chain_top(parser);
+    njs_parser_chain_top_set(parser, node);
+
+    return NXT_OK;
+}
+
+
 static njs_token_t
 njs_parser_grouping_expression(njs_vm_t *vm, njs_parser_t *parser)
 {
index 476fc2e39f00897770701ce2103923b3df700114..505d3452732c6c69a45c4fe6a5a1bad0c8141577 100644 (file)
@@ -25,12 +25,14 @@ struct njs_parser_scope_s {
     nxt_array_t                     *values[2];  /* Array of njs_value_t. */
     njs_index_t                     next_index[2];
 
+    nxt_str_t                       cwd;
     nxt_str_t                       file;
 
     njs_scope_t                     type:8;
     uint8_t                         nesting;     /* 4 bits */
     uint8_t                         argument_closures;
     uint8_t                         arguments_object;
+    uint8_t                         module;
 };
 
 
@@ -83,6 +85,7 @@ njs_token_t njs_parser_var_expression(njs_vm_t *vm, njs_parser_t *parser,
     njs_token_t token);
 njs_token_t njs_parser_assignment_expression(njs_vm_t *vm,
     njs_parser_t *parser, njs_token_t token);
+njs_token_t njs_parser_module_lambda(njs_vm_t *vm, njs_parser_t *parser);
 njs_token_t njs_parser_terminal(njs_vm_t *vm, njs_parser_t *parser,
     njs_token_t token);
 njs_token_t njs_parser_property_token(njs_vm_t *vm, njs_parser_t *parser);
@@ -108,6 +111,10 @@ void njs_parser_node_error(njs_vm_t *vm, njs_parser_node_t *node,
     ((vm)->options.accumulative && (scope)->type == NJS_SCOPE_GLOBAL)
 
 
+#define njs_parser_text(parser)                                               \
+    &(parser)->lexer->lexer_token->text
+
+
 #define njs_parser_syntax_error(vm, parser, fmt, ...)                         \
     njs_parser_lexer_error(vm, parser, NJS_OBJECT_SYNTAX_ERROR, fmt,          \
                            ##__VA_ARGS__)
index 8882b3ceff48f82883c9179b03a16779b907426a..d6b58e2e8d317e4403fbdcd6451af6706c660c90 100644 (file)
@@ -22,6 +22,8 @@
 
 typedef struct {
     char                    *file;
+    size_t                  n_paths;
+    char                    **paths;
     nxt_int_t               version;
     nxt_int_t               disassemble;
     nxt_int_t               interactive;
@@ -68,6 +70,7 @@ static nxt_int_t njs_console_init(njs_vm_t *vm, njs_console_t *console);
 static nxt_int_t njs_externals_init(njs_vm_t *vm, njs_console_t *console);
 static nxt_int_t njs_interactive_shell(njs_opts_t *opts,
     njs_vm_opt_t *vm_options);
+static njs_vm_t *njs_create_vm(njs_opts_t *opts, njs_vm_opt_t *vm_options);
 static nxt_int_t njs_process_file(njs_opts_t *opts, njs_vm_opt_t *vm_options);
 static nxt_int_t njs_process_script(njs_console_t *console, njs_opts_t *opts,
     const nxt_str_t *script);
@@ -246,6 +249,10 @@ main(int argc, char **argv)
         ret = njs_process_file(&opts, &vm_options);
     }
 
+    if (opts.paths != NULL) {
+        free(opts.paths);
+    }
+
     return (ret == NXT_OK) ? EXIT_SUCCESS : EXIT_FAILURE;
 }
 
@@ -263,6 +270,7 @@ njs_get_options(njs_opts_t *opts, int argc, char** argv)
         "  -d              print disassembled code.\n"
         "  -q              disable interactive introduction prompt.\n"
         "  -s              sandbox mode.\n"
+        "  -p              set path prefix for modules.\n"
         "  -v              print njs version and exit.\n"
         "  <filename> | -  run code from a file or stdin.\n";
 
@@ -298,6 +306,23 @@ njs_get_options(njs_opts_t *opts, int argc, char** argv)
             opts->sandbox = 1;
             break;
 
+        case 'p':
+            if (argv[++i] != NULL) {
+                opts->n_paths++;
+                opts->paths = realloc(opts->paths,
+                                      opts->n_paths * sizeof(char *));
+                if (opts->paths == NULL) {
+                    fprintf(stderr, "failed to add path\n");
+                    return NXT_ERROR;
+                }
+
+                opts->paths[opts->n_paths - 1] = argv[i];
+                break;
+            }
+
+            fprintf(stderr, "option \"-p\" requires directory name\n");
+            return NXT_ERROR;
+
         case 'v':
         case 'V':
             opts->version = 1;
@@ -383,14 +408,8 @@ njs_interactive_shell(njs_opts_t *opts, njs_vm_opt_t *vm_options)
         return NXT_ERROR;
     }
 
-    vm = njs_vm_create(vm_options);
+    vm = njs_create_vm(opts, vm_options);
     if (vm == NULL) {
-        fprintf(stderr, "failed to create vm\n");
-        return NXT_ERROR;
-    }
-
-    if (njs_externals_init(vm, vm_options->external) != NXT_OK) {
-        fprintf(stderr, "failed to add external protos\n");
         return NXT_ERROR;
     }
 
@@ -511,16 +530,8 @@ njs_process_file(njs_opts_t *opts, njs_vm_opt_t *vm_options)
         script.length += n;
     }
 
-    vm = njs_vm_create(vm_options);
+    vm = njs_create_vm(opts, vm_options);
     if (vm == NULL) {
-        fprintf(stderr, "failed to create vm\n");
-        ret = NXT_ERROR;
-        goto done;
-    }
-
-    ret = njs_externals_init(vm, vm_options->external);
-    if (ret != NXT_OK) {
-        fprintf(stderr, "failed to add external protos\n");
         ret = NXT_ERROR;
         goto done;
     }
@@ -549,6 +560,65 @@ close_fd:
 }
 
 
+static njs_vm_t *
+njs_create_vm(njs_opts_t *opts, njs_vm_opt_t *vm_options)
+{
+    char        *p, *start;
+    njs_vm_t    *vm;
+    nxt_int_t   ret;
+    nxt_str_t   path;
+    nxt_uint_t  i;
+
+    vm = njs_vm_create(vm_options);
+    if (vm == NULL) {
+        fprintf(stderr, "failed to create vm\n");
+        return NULL;
+    }
+
+    if (njs_externals_init(vm, vm_options->external) != NXT_OK) {
+        fprintf(stderr, "failed to add external protos\n");
+        return NULL;
+    }
+
+    for (i = 0; i < opts->n_paths; i++) {
+        path.start = (u_char *) opts->paths[i];
+        path.length = strlen(opts->paths[i]);
+
+        ret = njs_vm_add_path(vm, &path);
+        if (ret != NXT_OK) {
+            fprintf(stderr, "failed to add path\n");
+            return NULL;
+        }
+    }
+
+    start = getenv("NJS_PATH");
+    if (start == NULL) {
+        return vm;
+    }
+
+    for ( ;; ) {
+        p = strchr(start, ':');
+
+        path.start = (u_char *) start;
+        path.length = (p != NULL) ? (size_t) (p - start) : strlen(start);
+
+        ret = njs_vm_add_path(vm, &path);
+        if (ret != NXT_OK) {
+            fprintf(stderr, "failed to add path\n");
+            return NULL;
+        }
+
+        if (p == NULL) {
+            break;
+        }
+
+        start = p + 1;
+    }
+
+    return vm;
+}
+
+
 static void
 njs_output(njs_vm_t *vm, njs_opts_t *opts, njs_ret_t ret)
 {
index b692b6f17b13d8c0c518e4dd9a3b5b4782baa052..ddaa531841f30d62c7c69ec0e34f89aacbb0e700 100644 (file)
@@ -488,7 +488,7 @@ njs_variable_reference_resolve(njs_vm_t *vm, njs_variable_reference_t *vr,
             return NXT_OK;
         }
 
-        if (scope->parent == NULL) {
+        if (scope->module || scope->parent == NULL) {
             /* A global scope. */
             vr->scope = scope;
 
index 60d54b076d3358fda6566e207726b2869e261979..cc248fe3138291409bf46973f2ee93c3fe35e0e2 100644 (file)
@@ -96,6 +96,7 @@ const njs_value_t  njs_string_memory_error = njs_string("MemoryError");
 
 
 const nxt_str_t  njs_entry_main =           nxt_string("main");
+const nxt_str_t  njs_entry_module =         nxt_string("module");
 const nxt_str_t  njs_entry_native =         nxt_string("native");
 const nxt_str_t  njs_entry_unknown =        nxt_string("unknown");
 const nxt_str_t  njs_entry_anonymous =      nxt_string("anonymous");
index 29defef80b0e3cb6c84acc21716f52f8dce65b17..2e0ca7d89515847b348ad20ee9f024edd52d0e87 100644 (file)
@@ -1024,6 +1024,8 @@ struct njs_vm_s {
     /* njs_vm_t must be aligned to njs_value_t due to scratch value. */
     njs_value_t              retval;
 
+    nxt_array_t              *paths;
+
     u_char                   *current;
 
     njs_value_t              *scopes[NJS_SCOPES];
@@ -1040,6 +1042,8 @@ struct njs_vm_s {
 
     nxt_lvlhsh_t             variables_hash;
     nxt_lvlhsh_t             values_hash;
+
+    nxt_array_t              *modules;
     nxt_lvlhsh_t             modules_hash;
 
     uint32_t                 event_id;
@@ -1301,6 +1305,7 @@ extern const njs_value_t  njs_string_internal_error;
 extern const njs_value_t  njs_string_memory_error;
 
 extern const nxt_str_t    njs_entry_main;
+extern const nxt_str_t    njs_entry_module;
 extern const nxt_str_t    njs_entry_native;
 extern const nxt_str_t    njs_entry_unknown;
 extern const nxt_str_t    njs_entry_anonymous;
diff --git a/njs/test/module/empty.js b/njs/test/module/empty.js
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/njs/test/module/exception.js b/njs/test/module/exception.js
new file mode 100644 (file)
index 0000000..e573633
--- /dev/null
@@ -0,0 +1,4 @@
+import lib from 'lib3.js';
+
+lib.exception();
+
diff --git a/njs/test/module/export.js b/njs/test/module/export.js
new file mode 100644 (file)
index 0000000..7c5f09b
--- /dev/null
@@ -0,0 +1,4 @@
+var a = 1;
+
+export default {a}
+export default {a}
diff --git a/njs/test/module/export_non_default.js b/njs/test/module/export_non_default.js
new file mode 100644 (file)
index 0000000..e927db2
--- /dev/null
@@ -0,0 +1,3 @@
+var a = 1;
+
+export a {a}
diff --git a/njs/test/module/lib1.js b/njs/test/module/lib1.js
new file mode 100644 (file)
index 0000000..8bf87cc
--- /dev/null
@@ -0,0 +1,19 @@
+function hash() {
+    var h = crypto.createHash('md5');
+    var v = h.update('AB').digest('hex');
+    return v;
+}
+
+import crypto from 'crypto';
+
+var state = {count:0}
+
+function inc() {
+       state.count++;
+}
+
+function get() {
+       return state.count;
+}
+
+export default {hash, inc, get};
diff --git a/njs/test/module/lib2.js b/njs/test/module/lib2.js
new file mode 100644 (file)
index 0000000..aaaf767
--- /dev/null
@@ -0,0 +1,7 @@
+import lib3 from 'lib3.js';
+
+function hash() {
+    return lib3.hash();
+}
+
+export default {hash};
diff --git a/njs/test/module/lib3.js b/njs/test/module/lib3.js
new file mode 100644 (file)
index 0000000..c3f1929
--- /dev/null
@@ -0,0 +1,11 @@
+function hash() {
+    return sub.hash();
+}
+
+function exception() {
+    return sub.error();
+}
+
+import sub from './sub/sub1.js';
+
+export default {hash, exception};
diff --git a/njs/test/module/libs/hash.js b/njs/test/module/libs/hash.js
new file mode 100644 (file)
index 0000000..8769228
--- /dev/null
@@ -0,0 +1,9 @@
+function hash() {
+    var h = crypto.createHash('md5');
+    var v = h.update('AB').digest('hex');
+    return v;
+}
+
+import crypto from 'crypto';
+
+export default {hash};
diff --git a/njs/test/module/loading_exception.js b/njs/test/module/loading_exception.js
new file mode 100644 (file)
index 0000000..bd3ae56
--- /dev/null
@@ -0,0 +1,3 @@
+throw Error('loading exception');
+
+export default {};
diff --git a/njs/test/module/normal.js b/njs/test/module/normal.js
new file mode 100644 (file)
index 0000000..32e4964
--- /dev/null
@@ -0,0 +1,35 @@
+import lib1   from 'lib1.js';
+import lib2   from 'lib2.js';
+import lib1_2 from 'lib1.js';
+
+import crypto from 'crypto';
+var h = crypto.createHash('md5');
+var hash = h.update('AB').digest('hex');
+
+if (lib1.hash() != hash) {
+    console.log("failed!");
+}
+
+if (lib2.hash() != hash) {
+    console.log("failed!");
+}
+
+if (lib1.get() != 0) {
+    console.log("failed!");
+}
+
+if (lib1_2.get() != 0) {
+    console.log("failed!");
+}
+
+lib1.inc();
+
+if (lib1.get() != 1) {
+    console.log("failed!");
+}
+
+if (lib1_2.get() != 1) {
+    console.log("failed!");
+}
+
+console.log("passed!");
diff --git a/njs/test/module/recursive.js b/njs/test/module/recursive.js
new file mode 100644 (file)
index 0000000..8687097
--- /dev/null
@@ -0,0 +1,3 @@
+
+
+import lib from './recursive.js';
diff --git a/njs/test/module/return.js b/njs/test/module/return.js
new file mode 100644 (file)
index 0000000..583ceb4
--- /dev/null
@@ -0,0 +1 @@
+return 1;
diff --git a/njs/test/module/sub/sub1.js b/njs/test/module/sub/sub1.js
new file mode 100644 (file)
index 0000000..1b880a6
--- /dev/null
@@ -0,0 +1,12 @@
+function hash() {
+    return sub2.hash(crypto);
+}
+
+function error() {
+    return {}.a.a;
+}
+
+import sub2 from 'sub2.js';
+import crypto from 'crypto';
+
+export default {hash, error};
diff --git a/njs/test/module/sub/sub2.js b/njs/test/module/sub/sub2.js
new file mode 100644 (file)
index 0000000..b8e5abe
--- /dev/null
@@ -0,0 +1,7 @@
+function hash(crypto) {
+    return hashlib.hash();
+}
+
+import hashlib from 'hash.js';
+
+export default {hash};
index 748a0ae67ae35ed726c5df3f5d4e137e906843b4..dbd2dd0146355fc3e0c0d49854806a7f843aec36 100644 (file)
@@ -9,7 +9,7 @@ proc njs_test {body {opts ""}} {
         spawn  -nottycopy njs
 
     } else {
-        spawn  -nottycopy njs $opts
+        eval spawn  -nottycopy njs $opts
     }
 
     expect -re "interactive njs \\d+\.\\d+\.\\d+\r\n\r"
@@ -31,7 +31,7 @@ type console.help() for more information\r
 }
 
 proc njs_run {opts output} {
-    spawn  -nottycopy njs $opts
+    eval spawn  -nottycopy njs $opts
     expect -re $output
     expect eof
 }
@@ -603,6 +603,26 @@ njs_test {
      "'ABCABC'\r\n>> "}
 }
 
+# Modules
+
+njs_run "-p njs/test/module/libs ./njs/test/module/normal.js" \
+        "passed!"
+
+njs_run "-p njs/test/module/libs/ ./njs/test/module/normal.js" \
+        "passed!"
+
+njs_run "-p njs/test/module -p njs/test/module/libs ./njs/test/module/normal.js" \
+        "passed!"
+
+njs_run "./njs/test/module/normal.js" \
+        "SyntaxError: Cannot find module \"hash.js\" in sub2.js:5"
+
+njs_run "-p njs/test/module/libs ./njs/test/module/exception.js" \
+        "at error \\(sub1.js:5\\)\r\n    at exception \\(lib3.js:5\\)"
+
+njs_run "-p njs/test/module ./njs/test/module/recursive.js" \
+        "SyntaxError: Cannot import itself \"./recursive.js\" in recursive.js:3"
+
 # CLI OPTIONS
 
 # help
@@ -635,3 +655,31 @@ njs_test {
     {"var crypto = require('crypto')\r\n"
      "undefined\r\n"}
 } "-s"
+
+# modules
+
+njs_test {
+    {"import lib1 from 'lib1.js'\r\n"
+     "undefined\r\n"}
+    {"import lib2 from 'lib1.js'\r\n"
+     "undefined\r\n"}
+    {"lib2.inc()\r\n"
+     "undefined\r\n"}
+    {"lib1.get()\r\n"
+     "1\r\n"}
+    {"import m from 'return.js'\r\n"
+     "Illegal return statement in return.js:1\r\n"}
+    {"import m from 'empty.js'\r\n"
+     "export statement is required in empty.js:1\r\n"}
+    {"import m from 'export.js'\r\n"
+     "Identifier \"default\" has already been declared in export.js:5\r\n"}
+    {"import m from 'export_non_default.js'\r\n"
+     "Non-default export is not supported in export_non_default.js:3\r\n"}
+    {"import m from 'loading_exception.js'\r\n"
+     "Error: loading exception\r\n    at module \\(loading_exception.js:1\\)"}
+    {"import lib3 from 'lib1.js'\r\n"
+     "undefined\r\n"}
+} "-p njs/test/module/"
+
+njs_run "-p njs/test/module/libs/ -d ./njs/test/module/normal.js" \
+        "passed!"
index e11a5054ba9fd99fdc6a35a12553fd76c1a75655..c3defffb5c465f85b10133358ee26a2b686359b9 100644 (file)
@@ -11481,6 +11481,35 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("typeof(null) === \"object\""),
       nxt_string("true") },
 
+    /* Module. */
+
+    { nxt_string("import;"),
+      nxt_string("SyntaxError: Non-default import is not supported in 1") },
+
+    { nxt_string("import {x} from y"),
+      nxt_string("SyntaxError: Non-default import is not supported in 1") },
+
+    { nxt_string("import x from y"),
+      nxt_string("SyntaxError: Unexpected token \"y\" in 1") },
+
+    { nxt_string("import x from {"),
+      nxt_string("SyntaxError: Unexpected token \"{\" in 1") },
+
+    { nxt_string("import x from ''"),
+      nxt_string("SyntaxError: Cannot find module \"\" in 1") },
+
+    { nxt_string("import x from 'crypto'"),
+      nxt_string("undefined") },
+
+    { nxt_string("import x from 'crypto' 1"),
+      nxt_string("SyntaxError: Unexpected token \"1\" in 1") },
+
+    { nxt_string("if (1) {import x from 'crypto'}"),
+      nxt_string("SyntaxError: Illegal import statement in 1") },
+
+    { nxt_string("export"),
+      nxt_string("SyntaxError: Illegal export statement in 1") },
+
 };