From f57fb7d3828cdd98107c87e879716eebfcf4a16e Mon Sep 17 00:00:00 2001 From: hongzhidao Date: Sat, 2 Mar 2019 20:31:10 +0800 Subject: [PATCH] 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. --- njs/njs.c | 31 ++ njs/njs.h | 2 + njs/njs_builtin.c | 2 + njs/njs_core.h | 4 +- njs/njs_generator.c | 96 +++++- 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/empty.js | 0 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 ++ 31 files changed, 1190 insertions(+), 39 deletions(-) create mode 100644 njs/test/module/empty.js create mode 100644 njs/test/module/exception.js create mode 100644 njs/test/module/export.js create mode 100644 njs/test/module/export_non_default.js create mode 100644 njs/test/module/lib1.js create mode 100644 njs/test/module/lib2.js create mode 100644 njs/test/module/lib3.js create mode 100644 njs/test/module/libs/hash.js create mode 100644 njs/test/module/loading_exception.js create mode 100644 njs/test/module/normal.js create mode 100644 njs/test/module/recursive.js create mode 100644 njs/test/module/return.js create mode 100644 njs/test/module/sub/sub1.js create mode 100644 njs/test/module/sub/sub2.js diff --git a/njs/njs.c b/njs/njs.c index acbd83dc..fa1e560f 100644 --- 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) { diff --git a/njs/njs.h b/njs/njs.h index 631cbd61..14773424 100644 --- 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, diff --git a/njs/njs_builtin.c b/njs/njs_builtin.c index 7976eabf..985e5f48 100644 --- a/njs/njs_builtin.c +++ b/njs/njs_builtin.c @@ -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 --git a/njs/njs_core.h b/njs/njs_core.h index 9b25d76c..f8ec3d23 100644 --- a/njs/njs_core.h +++ b/njs/njs_core.h @@ -7,6 +7,7 @@ #ifndef _NJS_CORE_H_INCLUDED_ #define _NJS_CORE_H_INCLUDED_ + #include #include @@ -46,7 +47,8 @@ #include #include - #include +#include + #endif /* _NJS_CORE_H_INCLUDED_ */ diff --git a/njs/njs_generator.c b/njs/njs_generator.c index b2ae85e0..bb0a0cc2 100644 --- a/njs/njs_generator.c +++ b/njs/njs_generator.c @@ -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; } diff --git a/njs/njs_lexer.h b/njs/njs_lexer.h index caae37b2..62712ac8 100644 --- a/njs/njs_lexer.h +++ b/njs/njs_lexer.h @@ -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 --git a/njs/njs_lexer_keyword.c b/njs/njs_lexer_keyword.c index bd7ddb92..f1d6d071 100644 --- a/njs/njs_lexer_keyword.c +++ b/njs/njs_lexer_keyword.c @@ -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 --git a/njs/njs_module.c b/njs/njs_module.c index 5aa974d3..decb2741 100644 --- a/njs/njs_module.c +++ b/njs/njs_module.c @@ -8,6 +8,369 @@ #include #include #include +#include +#include +#include +#include +#include +#include + + +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) { diff --git a/njs/njs_module.h b/njs/njs_module.h index 697d2a1c..63e86b9a 100644 --- a/njs/njs_module.h +++ b/njs/njs_module.h @@ -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 --git a/njs/njs_parser.c b/njs/njs_parser.c index 9329acd3..9b6b6a7b 100644 --- a/njs/njs_parser.c +++ b/njs/njs_parser.c @@ -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) { diff --git a/njs/njs_parser.h b/njs/njs_parser.h index 476fc2e3..505d3452 100644 --- a/njs/njs_parser.h +++ b/njs/njs_parser.h @@ -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__) diff --git a/njs/njs_shell.c b/njs/njs_shell.c index 8882b3ce..d6b58e2e 100644 --- a/njs/njs_shell.c +++ b/njs/njs_shell.c @@ -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" " | - 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) { diff --git a/njs/njs_variable.c b/njs/njs_variable.c index b692b6f1..ddaa5318 100644 --- a/njs/njs_variable.c +++ b/njs/njs_variable.c @@ -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; diff --git a/njs/njs_vm.c b/njs/njs_vm.c index 60d54b07..cc248fe3 100644 --- a/njs/njs_vm.c +++ b/njs/njs_vm.c @@ -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"); diff --git a/njs/njs_vm.h b/njs/njs_vm.h index 29defef8..2e0ca7d8 100644 --- a/njs/njs_vm.h +++ b/njs/njs_vm.h @@ -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 index 00000000..e69de29b diff --git a/njs/test/module/exception.js b/njs/test/module/exception.js new file mode 100644 index 00000000..e5736335 --- /dev/null +++ b/njs/test/module/exception.js @@ -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 index 00000000..7c5f09bb --- /dev/null +++ b/njs/test/module/export.js @@ -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 index 00000000..e927db2d --- /dev/null +++ b/njs/test/module/export_non_default.js @@ -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 index 00000000..8bf87cc2 --- /dev/null +++ b/njs/test/module/lib1.js @@ -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 index 00000000..aaaf767e --- /dev/null +++ b/njs/test/module/lib2.js @@ -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 index 00000000..c3f19297 --- /dev/null +++ b/njs/test/module/lib3.js @@ -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 index 00000000..87692286 --- /dev/null +++ b/njs/test/module/libs/hash.js @@ -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 index 00000000..bd3ae56f --- /dev/null +++ b/njs/test/module/loading_exception.js @@ -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 index 00000000..32e4964d --- /dev/null +++ b/njs/test/module/normal.js @@ -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 index 00000000..86870971 --- /dev/null +++ b/njs/test/module/recursive.js @@ -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 index 00000000..583ceb4e --- /dev/null +++ b/njs/test/module/return.js @@ -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 index 00000000..1b880a6f --- /dev/null +++ b/njs/test/module/sub/sub1.js @@ -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 index 00000000..b8e5abe6 --- /dev/null +++ b/njs/test/module/sub/sub2.js @@ -0,0 +1,7 @@ +function hash(crypto) { + return hashlib.hash(); +} + +import hashlib from 'hash.js'; + +export default {hash}; diff --git a/njs/test/njs_expect_test.exp b/njs/test/njs_expect_test.exp index 748a0ae6..dbd2dd01 100644 --- a/njs/test/njs_expect_test.exp +++ b/njs/test/njs_expect_test.exp @@ -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!" diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index e11a5054..c3defffb 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -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") }, + }; -- 2.47.3