[njs] Interactive shell: object level completions.
Dmitry Volyntsev
xeioex at nginx.com
Wed Oct 4 15:58:40 UTC 2017
details: http://hg.nginx.org/njs/rev/d45f6f0079ab
branches:
changeset: 411:d45f6f0079ab
user: Dmitry Volyntsev <xeioex at nginx.com>
date: Wed Oct 04 18:58:10 2017 +0300
description:
Interactive shell: object level completions.
diffstat:
Makefile | 7 +-
njs/njs.c | 89 +++++++++------
njs/njs_builtin.c | 247 ++++++++++++++++++++++++++++++++++++++----
njs/njscript.h | 2 +-
njs/test/njs_expect_test.exp | 168 +++++++++++++++++++++++++++++
nxt/auto/configure | 1 +
nxt/auto/expect | 31 +++++
7 files changed, 477 insertions(+), 68 deletions(-)
diffs (762 lines):
diff -r 94c42736a730 -r d45f6f0079ab Makefile
--- a/Makefile Tue Oct 03 21:24:58 2017 +0300
+++ b/Makefile Wed Oct 04 18:58:10 2017 +0300
@@ -78,13 +78,14 @@ all: test lib_test
njs: $(NXT_BUILDDIR)/njs
-test: \
+njs_interactive_test: njs_expect_test $(NXT_BUILDDIR)/njs_interactive_test
+ $(NXT_BUILDDIR)/njs_interactive_test
+
+test: njs_interactive_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)
diff -r 94c42736a730 -r d45f6f0079ab njs/njs.c
--- a/njs/njs.c Tue Oct 03 21:24:58 2017 +0300
+++ b/njs/njs.c Wed Oct 04 18:58:10 2017 +0300
@@ -50,7 +50,8 @@ typedef struct {
size_t index;
size_t length;
njs_vm_t *vm;
- const char **completions;
+ nxt_array_t *completions;
+ nxt_array_t *suffix_completions;
nxt_lvlhsh_each_t lhe;
njs_completion_phase_t phase;
} njs_completion_t;
@@ -257,8 +258,7 @@ njs_interactive_shell(njs_opts_t *opts,
printf("interactive njscript\n\n");
- printf("v<Tab> -> the properties of v object.\n");
- printf("v.<Tab> -> all the available prototype methods.\n");
+ printf("v.<Tab> -> the properties and prototype methods of v.\n");
printf("type console.help() for more information\n\n");
for ( ;; ) {
@@ -487,7 +487,7 @@ njs_editline_init(njs_vm_t *vm)
rl_attempted_completion_function = njs_completion_handler;
rl_basic_word_break_characters = (char *) " \t\n\"\\'`@$><=;,|&{(";
- njs_completion.completions = njs_vm_completions(vm);
+ njs_completion.completions = njs_vm_completions(vm, NULL);
if (njs_completion.completions == NULL) {
return NXT_ERROR;
}
@@ -507,12 +507,18 @@ njs_completion_handler(const char *text,
}
+/* editline frees the buffer every time. */
+#define njs_editline(s) strndup((char *) (s)->start, (s)->length)
+
+#define njs_completion(c, i) &(((nxt_str_t *) (c)->start)[i])
+
static char *
njs_completion_generator(const char *text, int state)
{
char *completion;
size_t len;
- const char *name, *p;
+ nxt_str_t expression, *suffix;
+ const char *p;
njs_variable_t *var;
njs_completion_t *cmpl;
@@ -528,20 +534,20 @@ njs_completion_generator(const char *tex
if (cmpl->phase == NJS_COMPLETION_GLOBAL) {
for ( ;; ) {
- name = cmpl->completions[cmpl->index];
- if (name == NULL) {
+ if (cmpl->index >= cmpl->completions->items) {
break;
}
- cmpl->index++;
+ suffix = njs_completion(cmpl->completions, cmpl->index++);
- if (name[0] == '.') {
+ if (suffix->start[0] == '.' || suffix->length < cmpl->length) {
continue;
}
- if (strncmp(name, text, cmpl->length) == 0) {
- /* editline frees the buffer every time. */
- return strdup(name);
+ if (strncmp(text, (char *) suffix->start,
+ nxt_min(suffix->length, cmpl->length)) == 0)
+ {
+ return njs_editline(suffix);
}
}
@@ -549,22 +555,14 @@ njs_completion_generator(const char *tex
for ( ;; ) {
var = nxt_lvlhsh_each(&cmpl->vm->parser->scope->variables,
&cmpl->lhe);
- if (var == NULL) {
+ if (var == NULL || var->name.length < cmpl->length) {
break;
}
- if (strncmp((char *) var->name.start, text, cmpl->length)
- == 0)
+ if (strncmp(text, (char *) var->name.start,
+ nxt_min(var->name.length, 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;
+ return njs_editline(&var->name);
}
}
}
@@ -573,11 +571,30 @@ njs_completion_generator(const char *tex
return NULL;
}
+ /* Getting the longest prefix before a '.' */
+
+ p = &text[cmpl->length - 1];
+ while (p > text && *p != '.') { p--; }
+
+ if (*p != '.') {
+ return NULL;
+ }
+
+ expression.start = (u_char *) text;
+ expression.length = p - text;
+
+ cmpl->suffix_completions = njs_vm_completions(cmpl->vm, &expression);
+ if (cmpl->suffix_completions == NULL) {
+ return NULL;
+ }
+
cmpl->index = 0;
cmpl->phase = NJS_COMPLETION_SUFFIX;
}
- len = 1;
+ /* Getting the right-most suffix after a '.' */
+
+ len = 0;
p = &text[cmpl->length - 1];
while (p > text && *p != '.') {
@@ -585,31 +602,29 @@ njs_completion_generator(const char *tex
len++;
}
- if (*p != '.') {
- return NULL;
- }
+ p++;
for ( ;; ) {
- name = cmpl->completions[cmpl->index++];
- if (name == NULL) {
+ if (cmpl->index >= cmpl->suffix_completions->items) {
break;
}
- if (name[0] != '.') {
+ suffix = njs_completion(cmpl->suffix_completions, cmpl->index++);
+
+ if (len != 0 && strncmp((char *) suffix->start, p,
+ nxt_min(len, suffix->length)) != 0)
+ {
continue;
}
- if (strncmp(name, p, len) != 0) {
- continue;
- }
-
- len = strlen(name) + (p - text) + 2;
+ len = suffix->length + (p - text) + 1;
completion = malloc(len);
if (completion == NULL) {
return NULL;
}
- snprintf(completion, len, "%.*s%s", (int) (p - text), text, name);
+ snprintf(completion, len, "%.*s%.*s", (int) (p - text), text,
+ (int) suffix->length, suffix->start);
return completion;
}
diff -r 94c42736a730 -r d45f6f0079ab njs/njs_builtin.c
--- a/njs/njs_builtin.c Tue Oct 03 21:24:58 2017 +0300
+++ b/njs/njs_builtin.c Wed Oct 04 18:58:10 2017 +0300
@@ -8,6 +8,7 @@
#include <nxt_types.h>
#include <nxt_clang.h>
#include <nxt_string.h>
+#include <nxt_djb_hash.h>
#include <nxt_stub.h>
#include <nxt_array.h>
#include <nxt_lvlhsh.h>
@@ -39,7 +40,10 @@ typedef struct {
static nxt_int_t njs_builtin_completions(njs_vm_t *vm, size_t *size,
- const char **completions);
+ nxt_str_t *completions);
+static nxt_array_t *njs_vm_expression_completions(njs_vm_t *vm,
+ nxt_str_t *expression);
+static nxt_array_t *njs_object_completions(njs_vm_t *vm, njs_object_t *object);
const njs_object_init_t *njs_object_init[] = {
@@ -346,33 +350,40 @@ njs_builtin_objects_clone(njs_vm_t *vm)
}
-const char **
-njs_vm_completions(njs_vm_t *vm)
+nxt_array_t *
+njs_vm_completions(njs_vm_t *vm, nxt_str_t *expression)
{
- size_t size;
- const char **completions;
+ size_t size;
+ nxt_array_t *completions;
+
+ if (expression == NULL) {
+ if (njs_builtin_completions(vm, &size, NULL) != NXT_OK) {
+ return NULL;
+ }
+
+ completions = nxt_array_create(size, sizeof(nxt_str_t),
+ &njs_array_mem_proto,
+ vm->mem_cache_pool);
- if (njs_builtin_completions(vm, &size, NULL) != NXT_OK) {
- return NULL;
+ if (nxt_slow_path(completions == NULL)) {
+ return NULL;
+ }
+
+ if (njs_builtin_completions(vm, &size, completions->start) != NXT_OK) {
+ return NULL;
+ }
+
+ completions->items = size;
+
+ return completions;
}
- 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;
+ return njs_vm_expression_completions(vm, expression);
}
static nxt_int_t
-njs_builtin_completions(njs_vm_t *vm, size_t *size, const char **completions)
+njs_builtin_completions(njs_vm_t *vm, size_t *size, nxt_str_t *completions)
{
char *compl;
size_t n, len;
@@ -398,7 +409,7 @@ njs_builtin_completions(njs_vm_t *vm, si
}
if (completions != NULL) {
- completions[n++] = (char *) keyword->name.start;
+ completions[n++] = keyword->name;
} else {
n++;
@@ -433,7 +444,8 @@ njs_builtin_completions(njs_vm_t *vm, si
snprintf(compl, len, "%s.%s", njs_object_init[i]->name.start,
string.start);
- completions[n++] = (char *) compl;
+ completions[n].length = len;
+ completions[n++].start = (u_char *) compl;
} else {
n++;
@@ -465,13 +477,16 @@ njs_builtin_completions(njs_vm_t *vm, si
snprintf(compl, len, ".%s", string.start);
for (k = 0; k < n; k++) {
- if (strncmp(completions[k], compl, len) == 0) {
+ if (strncmp((char *) completions[k].start, compl, len)
+ == 0)
+ {
break;
}
}
if (k == n) {
- completions[n++] = (char *) compl;
+ completions[n].length = len;
+ completions[n++].start = (u_char *) compl;
}
} else {
@@ -504,7 +519,8 @@ njs_builtin_completions(njs_vm_t *vm, si
snprintf(compl, len, "%s.%s",
njs_constructor_init[i]->name.start, string.start);
- completions[n++] = (char *) compl;
+ completions[n].length = len;
+ completions[n++].start = (u_char *) compl;
} else {
n++;
@@ -533,7 +549,8 @@ njs_builtin_completions(njs_vm_t *vm, si
snprintf(compl, len, "%.*s",
(int) ext_object->name.length, ext_object->name.start);
- completions[n++] = (char *) compl;
+ completions[n].length = len;
+ completions[n++].start = (u_char *) compl;
} else {
n++;
@@ -557,7 +574,8 @@ njs_builtin_completions(njs_vm_t *vm, si
(int) ext_object->name.length, ext_object->name.start,
(int) ext_prop->name.length, ext_prop->name.start);
- completions[n++] = (char *) compl;
+ completions[n].length = len;
+ completions[n++].start = (u_char *) compl;
} else {
n++;
@@ -573,6 +591,181 @@ njs_builtin_completions(njs_vm_t *vm, si
}
+static nxt_array_t *
+njs_vm_expression_completions(njs_vm_t *vm, nxt_str_t *expression)
+{
+ u_char *p, *end;
+ nxt_int_t ret;
+ njs_value_t *value;
+ njs_variable_t *var;
+ njs_object_prop_t *prop;
+ nxt_lvlhsh_query_t lhq;
+
+ if (nxt_slow_path(vm->parser == NULL)) {
+ return NULL;
+ }
+
+ p = expression->start;
+ end = p + expression->length;
+
+ lhq.key.start = p;
+
+ while (p < end && *p != '.') { p++; }
+
+ lhq.proto = &njs_variables_hash_proto;
+ lhq.key.length = p - lhq.key.start;
+ lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length);
+
+ ret = nxt_lvlhsh_find(&vm->parser->scope->variables, &lhq);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return NULL;
+ }
+
+ var = lhq.value;
+ value = njs_vmcode_operand(vm, var->index);
+
+ if (!njs_is_object(value)) {
+ return NULL;
+ }
+
+ lhq.proto = &njs_object_hash_proto;
+
+ for ( ;; ) {
+
+ if (p == end) {
+ break;
+ }
+
+ lhq.key.start = ++p;
+
+ while (p < end && *p != '.') { p++; }
+
+ lhq.key.length = p - lhq.key.start;
+ lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length);
+
+ ret = nxt_lvlhsh_find(&value->data.u.object->hash, &lhq);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return NULL;
+ }
+
+ prop = lhq.value;
+
+ if (!njs_is_object(&prop->value)) {
+ return NULL;
+ }
+
+ value = &prop->value;
+ }
+
+ return njs_object_completions(vm, value->data.u.object);
+}
+
+
+static nxt_array_t *
+njs_object_completions(njs_vm_t *vm, njs_object_t *object)
+{
+ size_t size;
+ nxt_uint_t n, k;
+ nxt_str_t *compl;
+ nxt_array_t *completions;
+ njs_object_t *o;
+ njs_object_prop_t *prop;
+ nxt_lvlhsh_each_t lhe;
+
+ size = 0;
+ o = object;
+
+ do {
+ nxt_lvlhsh_each_init(&lhe, &njs_object_hash_proto);
+
+ for ( ;; ) {
+ prop = nxt_lvlhsh_each(&o->hash, &lhe);
+ if (prop == NULL) {
+ break;
+ }
+
+ size++;
+ }
+
+ nxt_lvlhsh_each_init(&lhe, &njs_object_hash_proto);
+
+ for ( ;; ) {
+ prop = nxt_lvlhsh_each(&o->shared_hash, &lhe);
+ if (prop == NULL) {
+ break;
+ }
+
+ size++;
+ }
+
+ o = o->__proto__;
+
+ } while (o != NULL);
+
+ completions = nxt_array_create(size, sizeof(nxt_str_t),
+ &njs_array_mem_proto, vm->mem_cache_pool);
+
+ if (nxt_slow_path(completions == NULL)) {
+ return NULL;
+ }
+
+ n = 0;
+ o = object;
+ compl = completions->start;
+
+ do {
+ nxt_lvlhsh_each_init(&lhe, &njs_object_hash_proto);
+
+ for ( ;; ) {
+ prop = nxt_lvlhsh_each(&o->hash, &lhe);
+ if (prop == NULL) {
+ break;
+ }
+
+ njs_string_get(&prop->name, &compl[n]);
+
+ for (k = 0; k < n; k++) {
+ if (nxt_strstr_eq(&compl[k], &compl[n])) {
+ break;
+ }
+ }
+
+ if (k == n) {
+ n++;
+ }
+ }
+
+ nxt_lvlhsh_each_init(&lhe, &njs_object_hash_proto);
+
+ for ( ;; ) {
+ prop = nxt_lvlhsh_each(&o->shared_hash, &lhe);
+ if (prop == NULL) {
+ break;
+ }
+
+ njs_string_get(&prop->name, &compl[n]);
+
+ for (k = 0; k < n; k++) {
+ if (nxt_strstr_eq(&compl[k], &compl[n])) {
+ break;
+ }
+ }
+
+ if (k == n) {
+ n++;
+ }
+ }
+
+ o = o->__proto__;
+
+ } while (o != NULL);
+
+ completions->items = n;
+
+ return completions;
+}
+
+
nxt_int_t
njs_builtin_match_native_function(njs_vm_t *vm, njs_function_t *function,
nxt_str_t *name)
diff -r 94c42736a730 -r d45f6f0079ab njs/njscript.h
--- a/njs/njscript.h Tue Oct 03 21:24:58 2017 +0300
+++ b/njs/njscript.h Wed Oct 04 18:58:10 2017 +0300
@@ -123,7 +123,7 @@ NXT_EXPORT void *njs_value_data(njs_valu
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);
+NXT_EXPORT nxt_array_t *njs_vm_completions(njs_vm_t *vm, nxt_str_t *expression);
extern const nxt_mem_proto_t njs_vm_mem_cache_pool_proto;
diff -r 94c42736a730 -r d45f6f0079ab njs/test/njs_expect_test.exp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/njs/test/njs_expect_test.exp Wed Oct 04 18:58:10 2017 +0300
@@ -0,0 +1,168 @@
+#
+# Copyright (C) Dmitry Volyntsev
+# Copyright (C) NGINX, Inc.
+#
+
+proc njs_test {body} {
+ spawn -nottycopy njs
+ expect "interactive njscript\r
+\r
+v.<Tab> -> the properties and prototype methods of v.\r
+type console.help() for more information\r
+\r
+>> "
+
+ set len [llength $body]
+ for {set i 0} {$i < $len} {incr i} {
+ set pair [lindex $body $i]
+ send [lindex $pair 0]
+ expect [lindex $pair 1]
+ }
+
+ # Ctrl-C
+ send \x03
+ expect eof
+}
+
+# simple multi line interation
+njs_test {
+ {"var a = 1\r\n"
+ "var a = 1\r\nundefined\r\n>> "}
+ {"a *= 2\r\n"
+ "a *= 2\r\n2\r\n>> "}
+}
+
+# Global completions, no
+njs_test {
+ {"\t\tn"
+ "\a\r\nDisplay all*possibilities? (y or n)*>> "}
+}
+
+# Global completions, yes
+njs_test {
+ {"\t\ty"
+ "\a\r\nDisplay all*possibilities? (y or n)*abstract"}
+}
+
+# Global completions, single partial match
+njs_test {
+ {"O\t"
+ "O\abject"}
+}
+
+njs_test {
+ {"M\t"
+ "M\aath"}
+}
+
+njs_test {
+ {"conso\t"
+ "conso\ale"}
+}
+
+# Global completions, multiple partial match
+njs_test {
+ {"cons\t\t"
+ "console*console.help*console.log*const"}
+}
+
+njs_test {
+ {"O\t"
+ "O\abject"}
+ {".\t\t"
+ "Object.create*Object.isSealed"}
+}
+
+njs_test {
+ {"Object.\t\t"
+ "Object.create*Object.isSealed"}
+}
+
+njs_test {
+ {"Object.g\t"
+ "Object.g\aet"}
+ {"\t"
+ "Object.getOwnPropertyDescriptor*Object.getPrototypeOf"}
+}
+
+njs_test {
+ {"M\t"
+ "M\aath"}
+ {".\t\t"
+ "Math.__proto__*Math.cbrt*Math.fround*Math.log2"}
+}
+
+# Global completions, no matches
+njs_test {
+ {"1.\t\t"
+ "1."}
+}
+
+njs_test {
+ {"1..\t\t"
+ "1.."}
+}
+
+njs_test {
+ {"'abc'.\t\t"
+ "'abc'."}
+}
+
+# Global completions, global vars
+njs_test {
+ {"var a = 1; var aa = 2\r\n"
+ "var a = 1; var aa = 2\r\nundefined\r\n>> "}
+ {"a\t\t"
+ "a*aa*abstract"}
+}
+
+njs_test {
+ {"var zz = 1\r\n"
+ "var zz = 1\r\nundefined\r\n>> "}
+ {"1 + z\t\r\n"
+ "1 + zz\r\n2"}
+}
+
+njs_test {
+ {"unknown_var\t\t"
+ "unknown_var"}
+}
+
+njs_test {
+ {"unknown_var.\t\t"
+ "unknown_var."}
+}
+
+# An object's level completions
+njs_test {
+ {"var o = {zz:1, zb:2}\r\n"
+ "var o = {zz:1, zb:2}\r\nundefined\r\n>> "}
+ {"o.z\t\t"
+ "o.zb*o.zz"}
+}
+
+njs_test {
+ {"var d = new Date()\r\n"
+ "var d = new Date()\r\nundefined\r\n>> "}
+ {"d.to\t\t"
+ "d.toDateString*d.toLocaleDateString*d.toString"}
+}
+
+njs_test {
+ {"var o = {a:new Date()}\r\n"
+ "var o = {a:new Date()}\r\nundefined\r\n>> "}
+ {"o.a.to\t\t"
+ "o.a.toDateString*o.a.toLocaleDateString*o.a.toString"}
+}
+
+# console object
+njs_test {
+ {"console.log()\r\n"
+ "console.log()\r\n\r\nundefined\r\n>> "}
+ {"console.log(1)\r\n"
+ "console.log(1)\r\n1\r\nundefined\r\n>> "}
+ {"console.log('abc')\r\n"
+ "console.log('abc')\r\nabc\r\nundefined\r\n>> "}
+ {"console.help()\r\n"
+ "console.help()\r\nVM built-in objects:"}
+}
diff -r 94c42736a730 -r d45f6f0079ab nxt/auto/configure
--- a/nxt/auto/configure Tue Oct 03 21:24:58 2017 +0300
+++ b/nxt/auto/configure Wed Oct 04 18:58:10 2017 +0300
@@ -56,3 +56,4 @@ END
. ${NXT_AUTO}getrandom
. ${NXT_AUTO}pcre
. ${NXT_AUTO}editline
+. ${NXT_AUTO}expect
diff -r 94c42736a730 -r d45f6f0079ab nxt/auto/expect
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nxt/auto/expect Wed Oct 04 18:58:10 2017 +0300
@@ -0,0 +1,31 @@
+
+# Copyright (C) Dmitry Volyntsev
+# Copyright (C) NGINX, Inc.
+
+nxt_found=no
+$nxt_echo -n "checking for expect ..."
+
+if /bin/sh -c "(expect -v)" >> $NXT_AUTOCONF_ERR 2>&1; then
+ nxt_found=yes
+fi
+
+if [ $nxt_found = yes ]; then
+ $nxt_echo " found"
+ $nxt_echo " + Expect version: `expect -v`"
+ cat << END >> $NXT_MAKEFILE_CONF
+
+njs_expect_test: njs njs/test/njs_expect_test.exp
+ PATH=\$(PATH):\$(NXT_BUILDDIR) expect -f njs/test/njs_expect_test.exp
+END
+
+else
+ $nxt_echo " not found"
+ $nxt_echo " - expect tests are disabled"
+
+ cat << END >> $NXT_MAKEFILE_CONF
+
+njs_expect_test:
+ @echo "Skipping expect tests"
+END
+
+fi
More information about the nginx-devel
mailing list