|
|
@ -9,6 +9,7 @@ |
|
|
|
/* Helper define to enable a feature set; can also use separate defines. */ |
|
|
|
#if defined(DUK_CMDLINE_FANCY) |
|
|
|
#define DUK_CMDLINE_LINENOISE |
|
|
|
#define DUK_CMDLINE_LINENOISE_COMPLETION |
|
|
|
#define DUK_CMDLINE_RLIMIT |
|
|
|
#define DUK_CMDLINE_SIGNAL |
|
|
|
#endif |
|
|
@ -292,6 +293,199 @@ static int wrapped_compile_execute(duk_context *ctx) { |
|
|
|
return 0; /* duk_safe_call() cleans up */ |
|
|
|
} |
|
|
|
|
|
|
|
/*
|
|
|
|
* Minimal Linenoise completion support |
|
|
|
*/ |
|
|
|
|
|
|
|
#if defined(DUK_CMDLINE_LINENOISE_COMPLETION) |
|
|
|
static duk_context *completion_ctx; |
|
|
|
|
|
|
|
static int completion_idpart(unsigned char c) { |
|
|
|
/* Very simplified "is identifier part" check. */ |
|
|
|
if ((c >= (unsigned char) 'a' && c <= (unsigned char) 'z') || |
|
|
|
(c >= (unsigned char) 'A' && c <= (unsigned char) 'Z') || |
|
|
|
(c >= (unsigned char) '0' && c <= (unsigned char) '9') || |
|
|
|
c == (unsigned char) '$' || c == (unsigned char) '_') { |
|
|
|
return 1; |
|
|
|
} |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
static int completion_digit(unsigned char c) { |
|
|
|
return (c >= (unsigned char) '0' && c <= (unsigned char) '9'); |
|
|
|
} |
|
|
|
|
|
|
|
static duk_ret_t linenoise_completion_lookup(duk_context *ctx) { |
|
|
|
duk_size_t len; |
|
|
|
const char *orig; |
|
|
|
const unsigned char *p; |
|
|
|
const unsigned char *p_curr; |
|
|
|
const unsigned char *p_end; |
|
|
|
const char *key; |
|
|
|
const char *prefix; |
|
|
|
linenoiseCompletions *lc; |
|
|
|
duk_idx_t idx_obj; |
|
|
|
|
|
|
|
orig = duk_require_string(ctx, -3); |
|
|
|
p_curr = (const unsigned char *) duk_require_lstring(ctx, -2, &len); |
|
|
|
p_end = p_curr + len; |
|
|
|
lc = duk_require_pointer(ctx, -1); |
|
|
|
|
|
|
|
duk_push_global_object(ctx); |
|
|
|
idx_obj = duk_require_top_index(ctx); |
|
|
|
|
|
|
|
while (p_curr <= p_end) { |
|
|
|
/* p_curr == p_end allowed on purpose, to handle 'Math.' for example. */ |
|
|
|
p = p_curr; |
|
|
|
while (p < p_end && p[0] != (unsigned char) '.') { |
|
|
|
p++; |
|
|
|
} |
|
|
|
/* 'p' points to a NUL (p == p_end) or a period. */ |
|
|
|
prefix = duk_push_lstring(ctx, (const char *) p_curr, (duk_size_t) (p - p_curr)); |
|
|
|
|
|
|
|
#if 0 |
|
|
|
fprintf(stderr, "Completion check: '%s'\n", prefix); |
|
|
|
fflush(stderr); |
|
|
|
#endif |
|
|
|
|
|
|
|
if (p == p_end) { |
|
|
|
/* 'idx_obj' points to the object matching the last
|
|
|
|
* full component, use [p_curr,p[ as a filter for |
|
|
|
* that object. |
|
|
|
*/ |
|
|
|
|
|
|
|
duk_enum(ctx, idx_obj, DUK_ENUM_INCLUDE_NONENUMERABLE); |
|
|
|
while (duk_next(ctx, -1, 0 /*get_value*/)) { |
|
|
|
key = duk_get_string(ctx, -1); |
|
|
|
#if 0 |
|
|
|
fprintf(stderr, "Key: %s\n", key ? key : ""); |
|
|
|
fflush(stderr); |
|
|
|
#endif |
|
|
|
if (!key) { |
|
|
|
/* Should never happen, just in case. */ |
|
|
|
goto next; |
|
|
|
} |
|
|
|
|
|
|
|
/* Ignore array index keys: usually not desirable, and would
|
|
|
|
* also require ['0'] quoting. |
|
|
|
*/ |
|
|
|
if (completion_digit(key[0])) { |
|
|
|
goto next; |
|
|
|
} |
|
|
|
|
|
|
|
/* XXX: There's no key quoting now, it would require replacing the
|
|
|
|
* last component with a ['foo\nbar'] style lookup when appropriate. |
|
|
|
*/ |
|
|
|
|
|
|
|
if (strlen(prefix) == 0) { |
|
|
|
/* Partial ends in a period, e.g. 'Math.' -> complete all Math properties. */ |
|
|
|
duk_push_string(ctx, orig); /* original, e.g. 'Math.' */ |
|
|
|
duk_push_string(ctx, key); |
|
|
|
duk_concat(ctx, 2); |
|
|
|
linenoiseAddCompletion(lc, duk_require_string(ctx, -1)); |
|
|
|
duk_pop(ctx); |
|
|
|
} else if (prefix && strcmp(key, prefix) == 0) { |
|
|
|
/* Full completion, add a period, e.g. input 'Math' -> 'Math.'. */ |
|
|
|
duk_push_string(ctx, orig); /* original, including partial last component */ |
|
|
|
duk_push_string(ctx, "."); |
|
|
|
duk_concat(ctx, 2); |
|
|
|
linenoiseAddCompletion(lc, duk_require_string(ctx, -1)); |
|
|
|
duk_pop(ctx); |
|
|
|
} else if (prefix && strncmp(key, prefix, strlen(prefix)) == 0) { |
|
|
|
/* Last component is partial, complete. */ |
|
|
|
duk_push_string(ctx, orig); /* original, including partial last component */ |
|
|
|
duk_push_string(ctx, key + strlen(prefix)); /* completion to last component */ |
|
|
|
duk_concat(ctx, 2); |
|
|
|
linenoiseAddCompletion(lc, duk_require_string(ctx, -1)); |
|
|
|
duk_pop(ctx); |
|
|
|
} |
|
|
|
|
|
|
|
next: |
|
|
|
duk_pop(ctx); |
|
|
|
} |
|
|
|
return 0; |
|
|
|
} else { |
|
|
|
if (duk_get_prop(ctx, idx_obj)) { |
|
|
|
duk_to_object(ctx, -1); /* for properties of plain strings etc */ |
|
|
|
duk_replace(ctx, idx_obj); |
|
|
|
p_curr = p + 1; |
|
|
|
} else { |
|
|
|
/* Not found. */ |
|
|
|
return 0; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
static void linenoise_completion(const char *buf, linenoiseCompletions *lc) { |
|
|
|
duk_context *ctx; |
|
|
|
const unsigned char *p_start; |
|
|
|
const unsigned char *p_end; |
|
|
|
const unsigned char *p; |
|
|
|
duk_int_t rc; |
|
|
|
|
|
|
|
if (!buf) { |
|
|
|
return; |
|
|
|
} |
|
|
|
ctx = completion_ctx; |
|
|
|
if (!ctx) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
p_start = (const unsigned char *) buf; |
|
|
|
p_end = (const unsigned char *) (buf + strlen(buf)); |
|
|
|
p = p_end; |
|
|
|
|
|
|
|
/* Scan backwards for a maximal string which looks like a property
|
|
|
|
* chain (e.g. foo.bar.quux). |
|
|
|
*/ |
|
|
|
|
|
|
|
while (--p >= p_start) { |
|
|
|
if (p[0] == (unsigned char) '.') { |
|
|
|
if (p <= p_start) { |
|
|
|
break; |
|
|
|
} |
|
|
|
if (!completion_idpart(p[-1])) { |
|
|
|
/* Catches e.g. 'foo..bar' -> we want 'bar' only. */ |
|
|
|
break; |
|
|
|
} |
|
|
|
} else if (!completion_idpart(p[0])) { |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
/* 'p' will either be p_start - 1 (ran out of buffer) or point to
|
|
|
|
* the first offending character. |
|
|
|
*/ |
|
|
|
p++; |
|
|
|
if (p < p_start || p >= p_end) { |
|
|
|
return; /* should never happen, but just in case */ |
|
|
|
} |
|
|
|
|
|
|
|
/* 'p' now points to a string of the form 'foo.bar.quux'. Look up
|
|
|
|
* all the components except the last; treat the last component as |
|
|
|
* a partial name which is used as a filter for the previous full |
|
|
|
* component. All lookups are from the global object now. |
|
|
|
*/ |
|
|
|
|
|
|
|
#if 0 |
|
|
|
fprintf(stderr, "Completion starting point: '%s'\n", p); |
|
|
|
fflush(stderr); |
|
|
|
#endif |
|
|
|
|
|
|
|
duk_push_string(ctx, (const char *) buf); |
|
|
|
duk_push_lstring(ctx, (const char *) p, (duk_size_t) (p_end - p)); |
|
|
|
duk_push_pointer(ctx, (void *) lc); |
|
|
|
|
|
|
|
rc = duk_safe_call(ctx, linenoise_completion_lookup, 3 /*nargs*/, 1 /*nrets*/); |
|
|
|
if (rc != DUK_EXEC_SUCCESS) { |
|
|
|
fprintf(stderr, "Completion handling failure: %s\n", duk_safe_to_string(ctx, -1)); |
|
|
|
} |
|
|
|
duk_pop(ctx); |
|
|
|
} |
|
|
|
#endif /* DUK_CMDLINE_LINENOISE_COMPLETION */ |
|
|
|
|
|
|
|
/*
|
|
|
|
* Execute from file handle etc |
|
|
|
*/ |
|
|
@ -498,6 +692,9 @@ static int handle_interactive(duk_context *ctx) { |
|
|
|
|
|
|
|
linenoiseSetMultiLine(1); |
|
|
|
linenoiseHistorySetMaxLen(64); |
|
|
|
#if defined(DUK_CMDLINE_LINENOISE_COMPLETION) |
|
|
|
linenoiseSetCompletionCallback(linenoise_completion); |
|
|
|
#endif |
|
|
|
|
|
|
|
for (;;) { |
|
|
|
if (buffer) { |
|
|
@ -505,7 +702,14 @@ static int handle_interactive(duk_context *ctx) { |
|
|
|
buffer = NULL; |
|
|
|
} |
|
|
|
|
|
|
|
#if defined(DUK_CMDLINE_LINENOISE_COMPLETION) |
|
|
|
completion_ctx = ctx; |
|
|
|
#endif |
|
|
|
buffer = linenoise(prompt); |
|
|
|
#if defined(DUK_CMDLINE_LINENOISE_COMPLETION) |
|
|
|
completion_ctx = NULL; |
|
|
|
#endif |
|
|
|
|
|
|
|
if (!buffer) { |
|
|
|
break; |
|
|
|
} |
|
|
|