From: Dmitry Volyntsev Date: Mon, 17 Jul 2017 17:38:00 +0000 (+0300) Subject: Interactive shell. X-Git-Tag: 0.1.12~7 X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=0fc78765bc1b4d97a965278080752e2033c7152c;p=njs.git Interactive shell. --- diff --git a/Makefile b/Makefile index e7d43fc2..a1df0c5b 100644 --- a/Makefile +++ b/Makefile @@ -74,11 +74,15 @@ $(NXT_BUILDDIR)/libnjs.a: \ all: test lib_test +njs: $(NXT_BUILDDIR)/njs + test: \ $(NXT_BUILDDIR)/njs_unit_test \ + $(NXT_BUILDDIR)/njs_interactive_test \ $(NXT_BUILDDIR)/njs_benchmark \ $(NXT_BUILDDIR)/njs_unit_test d + $(NXT_BUILDDIR)/njs_interactive_test clean: rm -rf $(NXT_BUILDDIR) @@ -386,6 +390,17 @@ $(NXT_BUILDDIR)/njs_disassembler.o: \ -I$(NXT_LIB) -Injs \ njs/njs_disassembler.c +$(NXT_BUILDDIR)/njs: \ + $(NXT_BUILDDIR)/libnxt.a \ + $(NXT_BUILDDIR)/libnjs.a \ + njs/njs.c \ + + $(NXT_CC) -o $(NXT_BUILDDIR)/njs $(NXT_CFLAGS) \ + -I$(NXT_LIB) -Injs \ + njs/njs.c \ + $(NXT_BUILDDIR)/libnjs.a \ + -lm $(NXT_PCRE_LIB) $(NXT_EDITLINE_LIB) + $(NXT_BUILDDIR)/njs_unit_test: \ $(NXT_BUILDDIR)/libnxt.a \ $(NXT_BUILDDIR)/libnjs.a \ @@ -397,6 +412,17 @@ $(NXT_BUILDDIR)/njs_unit_test: \ $(NXT_BUILDDIR)/libnjs.a \ -lm $(NXT_PCRE_LIB) +$(NXT_BUILDDIR)/njs_interactive_test: \ + $(NXT_BUILDDIR)/libnxt.a \ + $(NXT_BUILDDIR)/libnjs.a \ + njs/test/njs_interactive_test.c \ + + $(NXT_CC) -o $(NXT_BUILDDIR)/njs_interactive_test $(NXT_CFLAGS) \ + -I$(NXT_LIB) -Injs \ + njs/test/njs_interactive_test.c \ + $(NXT_BUILDDIR)/libnjs.a \ + -lm $(NXT_PCRE_LIB) + $(NXT_BUILDDIR)/njs_benchmark: \ $(NXT_BUILDDIR)/libnxt.a \ $(NXT_BUILDDIR)/libnjs.a \ diff --git a/njs/njs.c b/njs/njs.c new file mode 100644 index 00000000..4ff329fb --- /dev/null +++ b/njs/njs.c @@ -0,0 +1,466 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +typedef enum { + NJS_COMPLETION_GLOBAL = 0, + NJS_COMPLETION_SUFFIX, +} njs_completion_phase_t; + + +typedef struct { + char *file; + nxt_int_t disassemble; + nxt_int_t interactive; +} njs_opts_t; + + +typedef struct { + size_t index; + size_t length; + njs_vm_t *vm; + const char **completions; + nxt_lvlhsh_each_t lhe; + njs_completion_phase_t phase; +} njs_completion_t; + + +static nxt_int_t njs_get_options(njs_opts_t *opts, int argc, char **argv); +static nxt_int_t njs_interactive_shell(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_vm_t *vm, njs_opts_t *opts, + const nxt_str_t *script, nxt_str_t *out); +static nxt_int_t njs_editline_init(njs_vm_t *vm); +static char **njs_completion_handler(const char *text, int start, int end); +static char *njs_completion_generator(const char *text, int state); + + +static njs_completion_t njs_completion; + + +int +main(int argc, char **argv) +{ + nxt_int_t ret; + njs_opts_t opts; + njs_vm_opt_t vm_options; + nxt_mem_cache_pool_t *mcp; + + memset(&opts, 0, sizeof(njs_opts_t)); + opts.interactive = 1; + + ret = njs_get_options(&opts, argc, argv); + if (ret != NXT_OK) { + return (ret == NXT_DONE) ? EXIT_SUCCESS : EXIT_FAILURE; + } + + mcp = nxt_mem_cache_pool_create(&njs_vm_mem_cache_pool_proto, NULL, + NULL, 2 * nxt_pagesize(), 128, 512, 16); + if (nxt_slow_path(mcp == NULL)) { + return EXIT_FAILURE; + } + + memset(&vm_options, 0, sizeof(njs_vm_opt_t)); + + vm_options.mcp = mcp; + vm_options.accumulative = 1; + + if (opts.interactive) { + ret = njs_interactive_shell(&opts, &vm_options); + + } else { + ret = njs_process_file(&opts, &vm_options); + } + + return (ret == NXT_OK) ? EXIT_SUCCESS : EXIT_FAILURE; +} + + +static nxt_int_t +njs_get_options(njs_opts_t *opts, int argc, char** argv) +{ + char *p; + nxt_int_t i, ret; + + ret = NXT_DONE; + + for (i = 1; i < argc; i++) { + + p = argv[i]; + + if (p[0] != '-' || (p[0] == '-' && p[1] == '\0')) { + opts->interactive = 0; + opts->file = argv[i]; + continue; + } + + p++; + + switch (*p) { + case 'd': + opts->disassemble = 1; + break; + + default: + fprintf(stderr, "Unknown argument: \"%s\"\n", argv[i]); + ret = NXT_ERROR; + + /* Fall through. */ + + case 'h': + case '?': + printf("Usage: %s [|-] [-d]\n", argv[0]); + return ret; + } + } + + return NXT_OK; +} + + +static nxt_int_t +njs_interactive_shell(njs_opts_t *opts, njs_vm_opt_t *vm_options) +{ + njs_vm_t *vm; + nxt_int_t ret; + nxt_str_t line, out; + + vm = njs_vm_create(vm_options); + if (vm == NULL) { + fprintf(stderr, "failed to create vm\n"); + return NXT_ERROR; + } + + if (njs_editline_init(vm) != NXT_OK) { + fprintf(stderr, "failed to init completions\n"); + return NXT_ERROR; + } + + printf("interactive njscript\n"); + + for ( ;; ) { + line.start = (u_char *) readline(">> "); + if (line.start == NULL) { + break; + } + + line.length = strlen((char *) line.start); + if (line.length == 0) { + continue; + } + + add_history((char *) line.start); + + ret = njs_process_script(vm, opts, &line, &out); + if (ret != NXT_OK) { + printf("njs_process_script() failed\n"); + return NXT_ERROR; + } + + printf("%.*s\n", (int) out.length, out.start); + + /* editline allocs a new buffer every time. */ + free(line.start); + } + + return NXT_OK; +} + + +static nxt_int_t +njs_process_file(njs_opts_t *opts, njs_vm_opt_t *vm_options) +{ + int fd; + char *file; + u_char buf[4096], *p, *end; + size_t size; + ssize_t n; + njs_vm_t *vm; + nxt_int_t ret; + nxt_str_t out, script; + struct stat sb; + + file = opts->file; + + if (file[0] == '-' && file[1] == '\0') { + fd = STDIN_FILENO; + + } else { + fd = open(file, O_RDONLY); + if (fd == -1) { + fprintf(stderr, "failed to open file: '%s' (%s)\n", + file, strerror(errno)); + return NXT_ERROR; + } + } + + fstat(fd, &sb); + + size = sizeof(buf); + + if (S_ISREG(sb.st_mode) && sb.st_size) { + size = sb.st_size; + } + + script.length = 0; + script.start = realloc(NULL, size); + if (script.start == NULL) { + fprintf(stderr, "alloc failed while reading '%s'\n", file); + return NXT_ERROR; + } + + p = script.start; + end = p + size; + + for ( ;; ) { + n = read(fd, buf, sizeof(buf)); + + if (n == 0) { + break; + } + + if (n < 0) { + fprintf(stderr, "failed to read file: '%s' (%s)\n", + file, strerror(errno)); + return NXT_ERROR; + } + + if (p + n > end) { + size *= 2; + + script.start = realloc(script.start, size); + if (script.start == NULL) { + fprintf(stderr, "alloc failed while reading '%s'\n", file); + return NXT_ERROR; + } + + p = script.start; + end = p + size; + } + + memcpy(p, buf, n); + + p += n; + script.length += n; + } + + vm = njs_vm_create(vm_options); + if (vm == NULL) { + fprintf(stderr, "failed to create vm\n"); + return NXT_ERROR; + } + + ret = njs_process_script(vm, opts, &script, &out); + if (ret != NXT_OK) { + fprintf(stderr, "njs_process_script() failed\n"); + return NXT_ERROR; + } + + if (!opts->disassemble) { + printf("%.*s\n", (int) out.length, out.start); + } + + return NXT_OK; +} + + +static nxt_int_t +njs_process_script(njs_vm_t *vm, njs_opts_t *opts, const nxt_str_t *script, + nxt_str_t *out) +{ + u_char *start; + nxt_int_t ret; + + start = script->start; + + ret = njs_vm_compile(vm, &start, start + script->length); + + if (ret == NXT_OK) { + if (opts->disassemble) { + njs_disassembler(vm); + printf("\n"); + } + + ret = njs_vm_run(vm); + + if (ret == NXT_OK) { + if (njs_vm_retval(vm, out) != NXT_OK) { + return NXT_ERROR; + } + + } else { + njs_vm_exception(vm, out); + } + + } else { + njs_vm_exception(vm, out); + } + + return NXT_OK; +} + + +static nxt_int_t +njs_editline_init(njs_vm_t *vm) +{ + rl_completion_append_character = '\0'; + rl_attempted_completion_function = njs_completion_handler; + rl_basic_word_break_characters = (char *) " \t\n\"\\'`@$><=;,|&{("; + + njs_completion.completions = njs_vm_completions(vm); + if (njs_completion.completions == NULL) { + return NXT_ERROR; + } + + njs_completion.vm = vm; + + return NXT_OK; +} + + +static char ** +njs_completion_handler(const char *text, int start, int end) +{ + rl_attempted_completion_over = 1; + + return rl_completion_matches(text, njs_completion_generator); +} + + +static char * +njs_completion_generator(const char *text, int state) +{ + char *completion; + size_t len; + const char *name, *p; + njs_variable_t *var; + njs_completion_t *cmpl; + + cmpl = &njs_completion; + + if (state == 0) { + cmpl->index = 0; + cmpl->length = strlen(text); + cmpl->phase = NJS_COMPLETION_GLOBAL; + + nxt_lvlhsh_each_init(&cmpl->lhe, &njs_variables_hash_proto); + } + + if (cmpl->phase == NJS_COMPLETION_GLOBAL) { + for ( ;; ) { + name = cmpl->completions[cmpl->index]; + if (name == NULL) { + break; + } + + cmpl->index++; + + if (name[0] == '.') { + continue; + } + + if (strncmp(name, text, cmpl->length) == 0) { + /* editline frees the buffer every time. */ + return strdup(name); + } + } + + if (cmpl->vm->parser != NULL) { + for ( ;; ) { + var = nxt_lvlhsh_each(&cmpl->vm->parser->scope->variables, + &cmpl->lhe); + if (var == NULL) { + break; + } + + if (strncmp((char *) var->name.start, text, cmpl->length) + == 0) + { + completion = malloc(var->name.length + 1); + if (completion == NULL) { + return NULL; + } + + memcpy(completion, var->name.start, var->name.length); + completion[var->name.length] = '\0'; + + return completion; + } + } + } + + if (cmpl->length == 0) { + return NULL; + } + + cmpl->index = 0; + cmpl->phase = NJS_COMPLETION_SUFFIX; + } + + len = 1; + p = &text[cmpl->length - 1]; + + while (p > text && *p != '.') { + p--; + len++; + } + + if (*p != '.') { + return NULL; + } + + for ( ;; ) { + name = cmpl->completions[cmpl->index++]; + if (name == NULL) { + break; + } + + if (name[0] != '.') { + continue; + } + + if (strncmp(name, p, len) != 0) { + continue; + } + + len = strlen(name) + (p - text) + 2; + completion = malloc(len); + if (completion == NULL) { + return NULL; + } + + snprintf(completion, len, "%.*s%s", (int) (p - text), text, name); + return completion; + } + + return NULL; +} diff --git a/njs/njs_array.c b/njs/njs_array.c index 0fa1a1a5..ef9ac759 100644 --- a/njs/njs_array.c +++ b/njs/njs_array.c @@ -373,6 +373,7 @@ static const njs_object_prop_t njs_array_constructor_properties[] = const njs_object_init_t njs_array_constructor_init = { + nxt_string("Array"), njs_array_constructor_properties, nxt_nitems(njs_array_constructor_properties), }; @@ -2234,6 +2235,7 @@ static const njs_object_prop_t njs_array_prototype_properties[] = const njs_object_init_t njs_array_prototype_init = { + nxt_string("Array"), njs_array_prototype_properties, nxt_nitems(njs_array_prototype_properties), }; diff --git a/njs/njs_boolean.c b/njs/njs_boolean.c index d75476a1..f6839af5 100644 --- a/njs/njs_boolean.c +++ b/njs/njs_boolean.c @@ -78,6 +78,7 @@ static const njs_object_prop_t njs_boolean_constructor_properties[] = const njs_object_init_t njs_boolean_constructor_init = { + nxt_string("Boolean"), njs_boolean_constructor_properties, nxt_nitems(njs_boolean_constructor_properties), }; @@ -156,6 +157,7 @@ static const njs_object_prop_t njs_boolean_prototype_properties[] = const njs_object_init_t njs_boolean_prototype_init = { + nxt_string("Boolean"), njs_boolean_prototype_properties, nxt_nitems(njs_boolean_prototype_properties), }; diff --git a/njs/njs_builtin.c b/njs/njs_builtin.c index 58b64351..1ce47075 100644 --- a/njs/njs_builtin.c +++ b/njs/njs_builtin.c @@ -27,6 +27,7 @@ #include #include #include +#include typedef struct { @@ -35,6 +36,40 @@ typedef struct { } njs_function_init_t; +static nxt_int_t njs_builtin_completions(njs_vm_t *vm, size_t *size, + const char **completions); + + +static const njs_object_init_t *object_init[] = { + NULL, /* global this */ + &njs_math_object_init, /* Math */ +}; + + +static const njs_object_init_t *prototype_init[] = { + &njs_object_prototype_init, + &njs_array_prototype_init, + &njs_boolean_prototype_init, + &njs_number_prototype_init, + &njs_string_prototype_init, + &njs_function_prototype_init, + &njs_regexp_prototype_init, + &njs_date_prototype_init, +}; + + +static const njs_object_init_t *constructor_init[] = { + &njs_object_constructor_init, + &njs_array_constructor_init, + &njs_boolean_constructor_init, + &njs_number_constructor_init, + &njs_string_constructor_init, + &njs_function_constructor_init, + &njs_regexp_constructor_init, + &njs_date_constructor_init, +}; + + static njs_ret_t njs_prototype_function(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t unused) @@ -54,17 +89,6 @@ njs_builtin_objects_create(njs_vm_t *vm) njs_function_t *functions, *constructors; njs_object_prototype_t *prototypes; - static const njs_object_init_t *prototype_init[] = { - &njs_object_prototype_init, - &njs_array_prototype_init, - &njs_boolean_prototype_init, - &njs_number_prototype_init, - &njs_string_prototype_init, - &njs_function_prototype_init, - &njs_regexp_prototype_init, - &njs_date_prototype_init, - }; - static const njs_object_prototype_t prototype_values[] = { /* * GCC 4 complains about uninitialized .shared field, @@ -97,17 +121,6 @@ njs_builtin_objects_create(njs_vm_t *vm) .object = { .type = NJS_DATE } } }, }; - static const njs_object_init_t *constructor_init[] = { - &njs_object_constructor_init, - &njs_array_constructor_init, - &njs_boolean_constructor_init, - &njs_number_constructor_init, - &njs_string_constructor_init, - &njs_function_constructor_init, - &njs_regexp_constructor_init, - &njs_date_constructor_init, - }; - static const njs_function_init_t native_constructors[] = { /* SunC does not allow empty array initialization. */ { njs_object_constructor, { 0 } }, @@ -121,11 +134,6 @@ njs_builtin_objects_create(njs_vm_t *vm) { njs_date_constructor, { 0 } }, }; - static const njs_object_init_t *object_init[] = { - NULL, /* global this */ - &njs_math_object_init, /* Math */ - }; - static const njs_object_init_t *function_init[] = { &njs_eval_function_init, /* eval */ NULL, /* toString */ @@ -333,3 +341,176 @@ njs_builtin_objects_clone(njs_vm_t *vm) return NXT_OK; } + + +const char ** +njs_vm_completions(njs_vm_t *vm) +{ + size_t size; + const char **completions; + + if (njs_builtin_completions(vm, &size, NULL) != NXT_OK) { + return NULL; + } + + completions = nxt_mem_cache_zalloc(vm->mem_cache_pool, + sizeof(char *) * (size + 1)); + + if (completions == NULL) { + return NULL; + } + + if (njs_builtin_completions(vm, NULL, completions) != NXT_OK) { + return NULL; + } + + return completions; +} + + +static nxt_int_t +njs_builtin_completions(njs_vm_t *vm, size_t *size, const char **completions) +{ + char *compl; + size_t n, len; + nxt_str_t string; + nxt_uint_t i, k; + njs_object_t *objects; + njs_keyword_t *keyword; + njs_function_t *constructors; + njs_object_prop_t *prop; + nxt_lvlhsh_each_t lhe; + njs_object_prototype_t *prototypes; + + n = 0; + + nxt_lvlhsh_each_init(&lhe, &njs_keyword_hash_proto); + + for ( ;; ) { + keyword = nxt_lvlhsh_each(&vm->shared->keywords_hash, &lhe); + + if (keyword == NULL) { + break; + } + + if (completions != NULL) { + completions[n++] = (char *) keyword->name.start; + + } else { + n++; + } + } + + objects = vm->shared->objects; + + for (i = NJS_OBJECT_THIS; i < NJS_OBJECT_MAX; i++) { + if (object_init[i] == NULL) { + continue; + } + + nxt_lvlhsh_each_init(&lhe, &njs_object_hash_proto); + + for ( ;; ) { + prop = nxt_lvlhsh_each(&objects[i].shared_hash, &lhe); + + if (prop == NULL) { + break; + } + + if (completions != NULL) { + njs_string_get(&prop->name, &string); + len = object_init[i]->name.length + string.length + 2; + + compl = nxt_mem_cache_zalloc(vm->mem_cache_pool, len); + if (compl == NULL) { + return NXT_ERROR; + } + + snprintf(compl, len, "%s.%s", object_init[i]->name.start, + string.start); + + completions[n++] = (char *) compl; + + } else { + n++; + } + } + } + + prototypes = vm->shared->prototypes; + + for (i = NJS_PROTOTYPE_OBJECT; i < NJS_PROTOTYPE_MAX; i++) { + nxt_lvlhsh_each_init(&lhe, &njs_object_hash_proto); + + for ( ;; ) { + prop = nxt_lvlhsh_each(&prototypes[i].object.shared_hash, &lhe); + + if (prop == NULL) { + break; + } + + if (completions != NULL) { + njs_string_get(&prop->name, &string); + len = string.length + 2; + + compl = nxt_mem_cache_zalloc(vm->mem_cache_pool, len); + if (compl == NULL) { + return NXT_ERROR; + } + + snprintf(compl, len, ".%s", string.start); + + for (k = 0; k < n; k++) { + if (strncmp(completions[k], compl, len) == 0) { + break; + } + } + + if (k == n) { + completions[n++] = (char *) compl; + } + + } else { + n++; + } + } + } + + constructors = vm->shared->constructors; + + for (i = NJS_CONSTRUCTOR_OBJECT; i < NJS_CONSTRUCTOR_MAX; i++) { + nxt_lvlhsh_each_init(&lhe, &njs_object_hash_proto); + + for ( ;; ) { + prop = nxt_lvlhsh_each(&constructors[i].object.shared_hash, &lhe); + + if (prop == NULL) { + break; + } + + if (completions != NULL) { + njs_string_get(&prop->name, &string); + len = constructor_init[i]->name.length + string.length + 2; + + compl = nxt_mem_cache_zalloc(vm->mem_cache_pool, len); + if (compl == NULL) { + return NXT_ERROR; + } + + snprintf(compl, len, "%s.%s", constructor_init[i]->name.start, + string.start); + + completions[n++] = (char *) compl; + + } else { + n++; + } + } + } + + if (size) { + *size = n; + } + + return NXT_OK; +} diff --git a/njs/njs_date.c b/njs/njs_date.c index b1c4aa0d..8a170805 100644 --- a/njs/njs_date.c +++ b/njs/njs_date.c @@ -931,6 +931,7 @@ static const njs_object_prop_t njs_date_constructor_properties[] = const njs_object_init_t njs_date_constructor_init = { + nxt_string("Date"), njs_date_constructor_properties, nxt_nitems(njs_date_constructor_properties), }; @@ -2244,6 +2245,7 @@ static const njs_object_prop_t njs_date_prototype_properties[] = const njs_object_init_t njs_date_prototype_init = { + nxt_string("Date"), njs_date_prototype_properties, nxt_nitems(njs_date_prototype_properties), }; diff --git a/njs/njs_function.c b/njs/njs_function.c index 88eafad4..4ccc9388 100644 --- a/njs/njs_function.c +++ b/njs/njs_function.c @@ -497,6 +497,7 @@ static const njs_object_prop_t njs_function_constructor_properties[] = const njs_object_init_t njs_function_constructor_init = { + nxt_string("Function"), njs_function_constructor_properties, nxt_nitems(njs_function_constructor_properties), }; @@ -692,6 +693,7 @@ static const njs_object_prop_t njs_function_prototype_properties[] = const njs_object_init_t njs_function_prototype_init = { + nxt_string("Function"), njs_function_prototype_properties, nxt_nitems(njs_function_prototype_properties), }; @@ -724,6 +726,7 @@ static const njs_object_prop_t njs_eval_function_properties[] = const njs_object_init_t njs_eval_function_init = { + nxt_string("Function"), njs_eval_function_properties, nxt_nitems(njs_eval_function_properties), }; diff --git a/njs/njs_lexer_keyword.c b/njs/njs_lexer_keyword.c index e80f085d..a6f4313a 100644 --- a/njs/njs_lexer_keyword.c +++ b/njs/njs_lexer_keyword.c @@ -23,13 +23,6 @@ #include -typedef struct { - nxt_str_t name; - njs_token_t token; - double number; -} njs_keyword_t; - - static const njs_keyword_t njs_keywords[] = { /* Values. */ @@ -150,7 +143,7 @@ njs_keyword_hash_test(nxt_lvlhsh_query_t *lhq, void *data) } -static const nxt_lvlhsh_proto_t njs_keyword_hash_proto +const nxt_lvlhsh_proto_t njs_keyword_hash_proto nxt_aligned(64) = { NXT_LVLHSH_DEFAULT, diff --git a/njs/njs_math.c b/njs/njs_math.c index 500540c2..0a283131 100644 --- a/njs/njs_math.c +++ b/njs/njs_math.c @@ -1088,6 +1088,7 @@ static const njs_object_prop_t njs_math_object_properties[] = const njs_object_init_t njs_math_object_init = { + nxt_string("Math"), njs_math_object_properties, nxt_nitems(njs_math_object_properties), }; diff --git a/njs/njs_number.c b/njs/njs_number.c index a4678188..4e19d7de 100644 --- a/njs/njs_number.c +++ b/njs/njs_number.c @@ -528,6 +528,7 @@ static const njs_object_prop_t njs_number_constructor_properties[] = const njs_object_init_t njs_number_constructor_init = { + nxt_string("Number"), njs_number_constructor_properties, nxt_nitems(njs_number_constructor_properties), }; @@ -687,6 +688,7 @@ static const njs_object_prop_t njs_number_prototype_properties[] = const njs_object_init_t njs_number_prototype_init = { + nxt_string("Number"), njs_number_prototype_properties, nxt_nitems(njs_number_prototype_properties), }; diff --git a/njs/njs_object.c b/njs/njs_object.c index e1ba1e79..046a28c1 100644 --- a/njs/njs_object.c +++ b/njs/njs_object.c @@ -1153,6 +1153,7 @@ static const njs_object_prop_t njs_object_constructor_properties[] = const njs_object_init_t njs_object_constructor_init = { + nxt_string("Object"), njs_object_constructor_properties, nxt_nitems(njs_object_constructor_properties), }; @@ -1477,6 +1478,7 @@ static const njs_object_prop_t njs_object_prototype_properties[] = const njs_object_init_t njs_object_prototype_init = { + nxt_string("Object"), njs_object_prototype_properties, nxt_nitems(njs_object_prototype_properties), }; diff --git a/njs/njs_object.h b/njs/njs_object.h index 3dd98fb6..bee87d3e 100644 --- a/njs/njs_object.h +++ b/njs/njs_object.h @@ -32,6 +32,7 @@ typedef struct { struct njs_object_init_s { + nxt_str_t name; const njs_object_prop_t *properties; nxt_uint_t items; }; diff --git a/njs/njs_parser.c b/njs/njs_parser.c index 85b8bcbe..20ff89a8 100644 --- a/njs/njs_parser.c +++ b/njs/njs_parser.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -95,17 +96,53 @@ static njs_token_t njs_parser_unexpected_token(njs_vm_t *vm, njs_parser_node_t * -njs_parser(njs_vm_t *vm, njs_parser_t *parser) +njs_parser(njs_vm_t *vm, njs_parser_t *parser, njs_parser_t *prev) { - njs_ret_t ret; - njs_token_t token; - njs_parser_node_t *node; + njs_ret_t ret; + njs_token_t token; + nxt_lvlhsh_t *variables, *prev_variables; + njs_variable_t *var; + njs_parser_node_t *node; + nxt_lvlhsh_each_t lhe; + nxt_lvlhsh_query_t lhq; ret = njs_parser_scope_begin(vm, parser, NJS_SCOPE_GLOBAL); if (nxt_slow_path(ret != NXT_OK)) { return NULL; } + if (prev != NULL) { + /* + * Copy the global scope variables from the previous + * iteration of the accumulative mode. + */ + nxt_lvlhsh_each_init(&lhe, &njs_variables_hash_proto); + + lhq.proto = &njs_variables_hash_proto; + lhq.replace = 0; + lhq.pool = vm->mem_cache_pool; + + variables = &parser->scope->variables; + prev_variables = &prev->scope->variables; + + for ( ;; ) { + var = nxt_lvlhsh_each(prev_variables, &lhe); + + if (var == NULL) { + break; + } + + lhq.value = var; + lhq.key = var->name; + lhq.key_hash = nxt_djb_hash(var->name.start, var->name.length); + + ret = nxt_lvlhsh_insert(variables, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + return NULL; + } + } + } + token = njs_parser_token(parser); while (token != NJS_TOKEN_END) { diff --git a/njs/njs_parser.h b/njs/njs_parser.h index 96ba7fd8..f0b85be7 100644 --- a/njs/njs_parser.h +++ b/njs/njs_parser.h @@ -334,6 +334,13 @@ struct njs_parser_s { }; +typedef struct { + nxt_str_t name; + njs_token_t token; + double number; +} njs_keyword_t; + + njs_token_t njs_lexer_token(njs_lexer_t *lexer); nxt_int_t njs_lexer_keywords_init(nxt_mem_cache_pool_t *mcp, nxt_lvlhsh_t *hash); @@ -341,7 +348,8 @@ njs_token_t njs_lexer_keyword(njs_lexer_t *lexer); njs_extern_t *njs_parser_external(njs_vm_t *vm, njs_parser_t *parser); -njs_parser_node_t *njs_parser(njs_vm_t *vm, njs_parser_t *parser); +njs_parser_node_t *njs_parser(njs_vm_t *vm, njs_parser_t *parser, + njs_parser_t *prev); njs_token_t njs_parser_arguments(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *parent); njs_token_t njs_parser_expression(njs_vm_t *vm, njs_parser_t *parser, @@ -375,4 +383,7 @@ nxt_int_t njs_generate_scope(njs_vm_t *vm, njs_parser_t *parser, } while (0) +extern const nxt_lvlhsh_proto_t njs_keyword_hash_proto; + + #endif /* _NJS_PARSER_H_INCLUDED_ */ diff --git a/njs/njs_regexp.c b/njs/njs_regexp.c index 8aa86a94..c64a449c 100644 --- a/njs/njs_regexp.c +++ b/njs/njs_regexp.c @@ -843,6 +843,7 @@ static const njs_object_prop_t njs_regexp_constructor_properties[] = const njs_object_init_t njs_regexp_constructor_init = { + nxt_string("RegExp"), njs_regexp_constructor_properties, nxt_nitems(njs_regexp_constructor_properties), }; @@ -903,6 +904,7 @@ static const njs_object_prop_t njs_regexp_prototype_properties[] = const njs_object_init_t njs_regexp_prototype_init = { + nxt_string("RegExp"), njs_regexp_prototype_properties, nxt_nitems(njs_regexp_prototype_properties), }; diff --git a/njs/njs_string.c b/njs/njs_string.c index 287cba8a..040c3887 100644 --- a/njs/njs_string.c +++ b/njs/njs_string.c @@ -425,6 +425,7 @@ static const njs_object_prop_t njs_string_constructor_properties[] = const njs_object_init_t njs_string_constructor_init = { + nxt_string("String"), njs_string_constructor_properties, nxt_nitems(njs_string_constructor_properties), }; @@ -3262,6 +3263,7 @@ static const njs_object_prop_t njs_string_prototype_properties[] = const njs_object_init_t njs_string_prototype_init = { + nxt_string("String"), njs_string_prototype_properties, nxt_nitems(njs_string_prototype_properties), }; diff --git a/njs/njs_variable.c b/njs/njs_variable.c index 0af2844c..9d2faad6 100644 --- a/njs/njs_variable.c +++ b/njs/njs_variable.c @@ -137,7 +137,7 @@ njs_variable_add(njs_vm_t *vm, njs_parser_t *parser, njs_variable_type_t type) return var; } - lhq.replace = 0; + lhq.replace = vm->accumulative; lhq.value = var; lhq.pool = vm->mem_cache_pool; @@ -355,21 +355,43 @@ njs_variable_get(njs_vm_t *vm, njs_parser_node_t *node) goto not_found; } - values = vs.scope->values[n]; + if (vm->accumulative && vs.scope->type == NJS_SCOPE_GLOBAL) { - if (values == NULL) { - values = nxt_array_create(4, sizeof(njs_value_t), &njs_array_mem_proto, - vm->mem_cache_pool); - if (nxt_slow_path(values == NULL)) { + /* + * When non-clonable VM runs in accumulative mode all + * global variables should be allocated in absolute scope + * to share them among consecutive VM invocations. + */ + + value = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t), + sizeof(njs_value_t)); + if (nxt_slow_path(value == NULL)) { return NULL; } - vs.scope->values[n] = values; - } + index = (njs_index_t) value; + + } else { + values = vs.scope->values[n]; + + if (values == NULL) { + values = nxt_array_create(4, sizeof(njs_value_t), + &njs_array_mem_proto, vm->mem_cache_pool); + if (nxt_slow_path(values == NULL)) { + return NULL; + } + + vs.scope->values[n] = values; + } + + value = nxt_array_add(values, &njs_array_mem_proto, vm->mem_cache_pool); + if (nxt_slow_path(value == NULL)) { + return NULL; + } + + index = vs.scope->next_index[n]; + vs.scope->next_index[n] += sizeof(njs_value_t); - value = nxt_array_add(values, &njs_array_mem_proto, vm->mem_cache_pool); - if (nxt_slow_path(value == NULL)) { - return NULL; } if (njs_is_object(&var->value)) { @@ -379,9 +401,6 @@ njs_variable_get(njs_vm_t *vm, njs_parser_node_t *node) *value = njs_value_void; } - index = vs.scope->next_index[n]; - vs.scope->next_index[n] += sizeof(njs_value_t); - var->index = index; node->index = index; diff --git a/njs/njs_vm.h b/njs/njs_vm.h index 06507504..14dfaef0 100644 --- a/njs/njs_vm.h +++ b/njs/njs_vm.h @@ -904,6 +904,7 @@ struct njs_vm_s { nxt_random_t random; uint8_t trailer; /* 1 bit */ + uint8_t accumulative; /* 1 bit */ }; diff --git a/njs/njscript.c b/njs/njscript.c index 1871ae7f..362dedea 100644 --- a/njs/njscript.c +++ b/njs/njscript.c @@ -65,7 +65,7 @@ njs_free(void *mem, void *p) } -static const nxt_mem_proto_t njs_vm_mem_cache_pool_proto = { +const nxt_mem_proto_t njs_vm_mem_cache_pool_proto = { njs_alloc, njs_zalloc, njs_align, @@ -175,6 +175,14 @@ njs_vm_create(njs_vm_opt_t *options) vm->trace.data = vm; vm->trailer = options->trailer; + + vm->accumulative = options->accumulative; + if (vm->accumulative) { + ret = njs_vm_init(vm); + if (nxt_slow_path(ret != NXT_OK)) { + return NULL; + } + } } return vm; @@ -193,7 +201,7 @@ njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end) { nxt_int_t ret; njs_lexer_t *lexer; - njs_parser_t *parser; + njs_parser_t *parser, *prev; njs_parser_node_t *node; parser = nxt_mem_cache_zalloc(vm->mem_cache_pool, sizeof(njs_parser_t)); @@ -201,6 +209,11 @@ njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end) return NJS_ERROR; } + if (vm->parser != NULL && !vm->accumulative) { + return NJS_ERROR; + } + + prev = vm->parser; vm->parser = parser; lexer = nxt_mem_cache_zalloc(vm->mem_cache_pool, sizeof(njs_lexer_t)); @@ -217,21 +230,27 @@ njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end) parser->code_size = sizeof(njs_vmcode_stop_t); parser->scope_offset = NJS_INDEX_GLOBAL_OFFSET; - node = njs_parser(vm, parser); + node = njs_parser(vm, parser, prev); if (nxt_slow_path(node == NULL)) { - return NJS_ERROR; + goto fail; } ret = njs_variables_scope_reference(vm, parser->scope); if (nxt_slow_path(ret != NXT_OK)) { - return NJS_ERROR; + goto fail; } *start = parser->lexer->start; + /* + * Reset the code array to prevent it from being disassembled + * again in the next iteration of the accumulative mode. + */ + vm->code = NULL; + ret = njs_generate_scope(vm, parser, node); if (nxt_slow_path(ret != NXT_OK)) { - return NJS_ERROR; + goto fail; } vm->current = parser->code_start; @@ -240,9 +259,13 @@ njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end) vm->scope_size = parser->scope_size; vm->variables_hash = parser->scope->variables; - vm->parser = NULL; - return NJS_OK; + +fail: + + vm->parser = prev; + + return NXT_ERROR; } @@ -255,6 +278,10 @@ njs_vm_clone(njs_vm_t *vm, nxt_mem_cache_pool_t *mcp, void **external) nxt_thread_log_debug("CLONE:"); + if (vm->accumulative) { + return NULL; + } + nmcp = mcp; if (nmcp == NULL) { diff --git a/njs/njscript.h b/njs/njscript.h index 65111d4c..28c11904 100644 --- a/njs/njscript.h +++ b/njs/njscript.h @@ -70,7 +70,8 @@ typedef struct { njs_vm_shared_t *shared; nxt_mem_cache_pool_t *mcp; - uint8_t trailer; /* 1 bit */ + uint8_t trailer; /* 1 bit */ + uint8_t accumulative; /* 1 bit */ } njs_vm_opt_t; @@ -113,5 +114,9 @@ NXT_EXPORT void *njs_value_data(njs_value_t *value); NXT_EXPORT nxt_int_t njs_value_string_copy(njs_vm_t *vm, nxt_str_t *retval, njs_value_t *value, uintptr_t *next); +NXT_EXPORT const char **njs_vm_completions(njs_vm_t *vm); + + +const nxt_mem_proto_t njs_vm_mem_cache_pool_proto; #endif /* _NJSCRIPT_H_INCLUDED_ */ diff --git a/njs/test/njs_interactive_test.c b/njs/test/njs_interactive_test.c new file mode 100644 index 00000000..b096f35a --- /dev/null +++ b/njs/test/njs_interactive_test.c @@ -0,0 +1,197 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +typedef struct { + nxt_str_t script; + nxt_str_t ret; +} njs_interactive_test_t; + + +#define ENTER "\n" + + +static njs_interactive_test_t njs_test[] = +{ + { nxt_string("var a = 3" ENTER + "a * 2" ENTER), + nxt_string("6") }, + + { nxt_string("var a = \"aa\\naa\"" ENTER + "a" ENTER), + nxt_string("aa\naa") }, + + { nxt_string("var a = 3" ENTER + "var a = 'str'" ENTER + "a" ENTER), + nxt_string("str") }, + + { nxt_string("var a = 2" ENTER + "a *= 2" ENTER + "a *= 2" ENTER + "a *= 2" ENTER), + nxt_string("16") }, + + { nxt_string("var a = 2" ENTER + "var b = 3" ENTER + "a * b" ENTER), + nxt_string("6") }, + + { nxt_string("var a = 2; var b = 3;" ENTER + "a * b" ENTER), + nxt_string("6") }, + + { nxt_string("function sq(f) { return f() * f() }" ENTER + "sq(function () { return 3 })" ENTER), + nxt_string("9") }, + + /* Error handling */ + + { nxt_string("var a = ;" ENTER + "2 + 2" ENTER), + nxt_string("4") }, + + { nxt_string("function f() { return b;" ENTER), + nxt_string("SyntaxError: Unexpected end of input in 1") }, + + { nxt_string("function f() { return b;" ENTER + "2 + 2" ENTER), + nxt_string("4") }, + + { nxt_string("function f() { return function() { return 1" ENTER + "2 + 2" ENTER), + nxt_string("4") }, + + { nxt_string("function f() { return b;}" ENTER + "2 + 2" ENTER), + nxt_string("4") }, + + { nxt_string("function f(o) { return o.a.a;}; f{{}}" ENTER + "2 + 2" ENTER), + nxt_string("4") }, + + { nxt_string("function ff(o) {return o.a.a}" ENTER + "function f(o) {return ff(o)}" ENTER + "f({})" ENTER), + nxt_string("TypeError") }, + + { nxt_string("function f(ff, o) {return ff(o)}" ENTER + "f(function (o) {return o.a.a}, {})" ENTER), + nxt_string("TypeError") }, + +}; + + +static nxt_int_t +njs_interactive_test(void) +{ + u_char *start, *last, *end; + njs_vm_t *vm; + nxt_int_t ret; + nxt_str_t s; + nxt_uint_t i; + nxt_bool_t success; + njs_vm_opt_t options; + nxt_mem_cache_pool_t *mcp; + njs_interactive_test_t *test; + + mcp = nxt_mem_cache_pool_create(&njs_vm_mem_cache_pool_proto, NULL, NULL, + 2 * nxt_pagesize(), 128, 512, 16); + if (nxt_slow_path(mcp == NULL)) { + return NXT_ERROR; + } + + ret = NXT_ERROR; + + for (i = 0; i < nxt_nitems(njs_test); i++) { + + test = &njs_test[i]; + + printf("\"%.*s\"\n", (int) test->script.length, test->script.start); + fflush(stdout); + + memset(&options, 0, sizeof(njs_vm_opt_t)); + + options.mcp = mcp; + options.accumulative = 1; + + vm = njs_vm_create(&options); + if (vm == NULL) { + goto fail; + } + + start = test->script.start; + last = start + test->script.length; + end = NULL; + + for ( ;; ) { + start = (end != NULL) ? end + 1 : start; + if (start >= last) { + break; + } + + end = (u_char *) strchr((char *) start, '\n'); + + ret = njs_vm_compile(vm, &start, end); + if (ret == NXT_OK) { + ret = njs_vm_run(vm); + } + } + + if (ret == NXT_OK) { + if (njs_vm_retval(vm, &s) != NXT_OK) { + goto fail; + } + + } else { + njs_vm_exception(vm, &s); + } + + success = nxt_strstr_eq(&test->ret, &s); + if (success) { + continue; + } + + printf("njs_interactive(\"%.*s\") failed: \"%.*s\" vs \"%.*s\"\n", + (int) test->script.length, test->script.start, + (int) test->ret.length, test->ret.start, + (int) s.length, s.start); + + goto fail; + } + + ret = NXT_OK; + + printf("njs interactive tests passed\n"); + +fail: + + nxt_mem_cache_pool_destroy(mcp); + + return ret; +} + + +int nxt_cdecl +main(int argc, char **argv) +{ + return njs_interactive_test(); +} diff --git a/nxt/auto/configure b/nxt/auto/configure index a4b828dd..0ea33a7b 100755 --- a/nxt/auto/configure +++ b/nxt/auto/configure @@ -55,3 +55,4 @@ END . ${NXT_AUTO}memalign . ${NXT_AUTO}getrandom . ${NXT_AUTO}pcre +. ${NXT_AUTO}editline diff --git a/nxt/auto/editline b/nxt/auto/editline new file mode 100644 index 00000000..45b06703 --- /dev/null +++ b/nxt/auto/editline @@ -0,0 +1,25 @@ +# Copyright (C) Dmitry Volyntsev +# Copyright (C) NGINX, Inc. + +nxt_feature="editline library" +nxt_feature_name=NXT_HAVE_EDITLINE +nxt_feature_run=no +nxt_feature_incs= +nxt_feature_libs=-ledit +nxt_feature_test="#include + + int main(void) { + add_history(NULL); + return 0; + }" +. ${NXT_AUTO}feature + +if [ $nxt_found = yes ]; then + cat << END >> $NXT_MAKEFILE_CONF +NXT_EDITLINE_LIB = -ledit +END + +else + $nxt_echo " - building interactive shell is not possible" + +fi