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)
-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 \
$(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 \
--- /dev/null
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <time.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include <nxt_auto_config.h>
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_string.h>
+#include <nxt_stub.h>
+#include <nxt_malloc.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_random.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_variable.h>
+#include <njs_parser.h>
+
+#include <editline/readline.h>
+
+
+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 [<file>|-] [-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;
+}
const njs_object_init_t njs_array_constructor_init = {
+ nxt_string("Array"),
njs_array_constructor_properties,
nxt_nitems(njs_array_constructor_properties),
};
const njs_object_init_t njs_array_prototype_init = {
+ nxt_string("Array"),
njs_array_prototype_properties,
nxt_nitems(njs_array_prototype_properties),
};
const njs_object_init_t njs_boolean_constructor_init = {
+ nxt_string("Boolean"),
njs_boolean_constructor_properties,
nxt_nitems(njs_boolean_constructor_properties),
};
const njs_object_init_t njs_boolean_prototype_init = {
+ nxt_string("Boolean"),
njs_boolean_prototype_properties,
nxt_nitems(njs_boolean_prototype_properties),
};
#include <njs_date.h>
#include <njs_math.h>
#include <string.h>
+#include <stdio.h>
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)
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,
.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 } },
{ 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 */
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;
+}
const njs_object_init_t njs_date_constructor_init = {
+ nxt_string("Date"),
njs_date_constructor_properties,
nxt_nitems(njs_date_constructor_properties),
};
const njs_object_init_t njs_date_prototype_init = {
+ nxt_string("Date"),
njs_date_prototype_properties,
nxt_nitems(njs_date_prototype_properties),
};
const njs_object_init_t njs_function_constructor_init = {
+ nxt_string("Function"),
njs_function_constructor_properties,
nxt_nitems(njs_function_constructor_properties),
};
const njs_object_init_t njs_function_prototype_init = {
+ nxt_string("Function"),
njs_function_prototype_properties,
nxt_nitems(njs_function_prototype_properties),
};
const njs_object_init_t njs_eval_function_init = {
+ nxt_string("Function"),
njs_eval_function_properties,
nxt_nitems(njs_eval_function_properties),
};
#include <string.h>
-typedef struct {
- nxt_str_t name;
- njs_token_t token;
- double number;
-} njs_keyword_t;
-
-
static const njs_keyword_t njs_keywords[] = {
/* Values. */
}
-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,
const njs_object_init_t njs_math_object_init = {
+ nxt_string("Math"),
njs_math_object_properties,
nxt_nitems(njs_math_object_properties),
};
const njs_object_init_t njs_number_constructor_init = {
+ nxt_string("Number"),
njs_number_constructor_properties,
nxt_nitems(njs_number_constructor_properties),
};
const njs_object_init_t njs_number_prototype_init = {
+ nxt_string("Number"),
njs_number_prototype_properties,
nxt_nitems(njs_number_prototype_properties),
};
const njs_object_init_t njs_object_constructor_init = {
+ nxt_string("Object"),
njs_object_constructor_properties,
nxt_nitems(njs_object_constructor_properties),
};
const njs_object_init_t njs_object_prototype_init = {
+ nxt_string("Object"),
njs_object_prototype_properties,
nxt_nitems(njs_object_prototype_properties),
};
struct njs_object_init_s {
+ nxt_str_t name;
const njs_object_prop_t *properties;
nxt_uint_t items;
};
#include <nxt_lvlhsh.h>
#include <nxt_random.h>
#include <nxt_mem_cache_pool.h>
+#include <nxt_djb_hash.h>
#include <njscript.h>
#include <njs_vm.h>
#include <njs_number.h>
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) {
};
+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);
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,
} while (0)
+extern const nxt_lvlhsh_proto_t njs_keyword_hash_proto;
+
+
#endif /* _NJS_PARSER_H_INCLUDED_ */
const njs_object_init_t njs_regexp_constructor_init = {
+ nxt_string("RegExp"),
njs_regexp_constructor_properties,
nxt_nitems(njs_regexp_constructor_properties),
};
const njs_object_init_t njs_regexp_prototype_init = {
+ nxt_string("RegExp"),
njs_regexp_prototype_properties,
nxt_nitems(njs_regexp_prototype_properties),
};
const njs_object_init_t njs_string_constructor_init = {
+ nxt_string("String"),
njs_string_constructor_properties,
nxt_nitems(njs_string_constructor_properties),
};
const njs_object_init_t njs_string_prototype_init = {
+ nxt_string("String"),
njs_string_prototype_properties,
nxt_nitems(njs_string_prototype_properties),
};
return var;
}
- lhq.replace = 0;
+ lhq.replace = vm->accumulative;
lhq.value = var;
lhq.pool = vm->mem_cache_pool;
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)) {
*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;
nxt_random_t random;
uint8_t trailer; /* 1 bit */
+ uint8_t accumulative; /* 1 bit */
};
}
-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,
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;
{
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));
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));
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;
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;
}
nxt_thread_log_debug("CLONE:");
+ if (vm->accumulative) {
+ return NULL;
+ }
+
nmcp = mcp;
if (nmcp == NULL) {
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;
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_ */
--- /dev/null
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_auto_config.h>
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_string.h>
+#include <nxt_stub.h>
+#include <nxt_malloc.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/resource.h>
+#include <time.h>
+
+
+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();
+}
. ${NXT_AUTO}memalign
. ${NXT_AUTO}getrandom
. ${NXT_AUTO}pcre
+. ${NXT_AUTO}editline
--- /dev/null
+# 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 <editline/readline.h>
+
+ 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