aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/eval.c
diff options
context:
space:
mode:
authorMichael Ennen <mike.ennen@gmail.com>2016-12-14 17:06:36 -0700
committerMichael Ennen <mike.ennen@gmail.com>2017-02-14 17:38:16 -0700
commitbb7d0deb2f93bd7980a51dca05ae222e751ab632 (patch)
treee6b13f4f16dde3eb738cde39b2f3b8db86623eb2 /src/nvim/eval.c
parent6c423989fc5becb294dacedceaac0c2e878a3858 (diff)
downloadrneovim-bb7d0deb2f93bd7980a51dca05ae222e751ab632.tar.gz
rneovim-bb7d0deb2f93bd7980a51dca05ae222e751ab632.tar.bz2
rneovim-bb7d0deb2f93bd7980a51dca05ae222e751ab632.zip
vim-patch:7.4.2044
Problem: filter() and map() either require a string or defining a function. Solution: Support lambda, a short way to define a function that evaluates an expression. (Yasuhiro Matsumoto, Ken Takata) https://github.com/vim/vim/commit/069c1e7fa9f45a665064f7f2c17da84d6a48f544
Diffstat (limited to 'src/nvim/eval.c')
-rw-r--r--src/nvim/eval.c368
1 files changed, 286 insertions, 82 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 017bb92c75..bf5ee7a530 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -4301,8 +4301,12 @@ static int eval7(
case '[': ret = get_list_tv(arg, rettv, evaluate);
break;
+ // Lambda: {arg, arg -> expr}
// Dictionary: {key: val, key: val}
- case '{': ret = get_dict_tv(arg, rettv, evaluate);
+ case '{': ret = get_lambda_tv(arg, rettv, evaluate);
+ if (ret == NOTDONE) {
+ ret = get_dict_tv(arg, rettv, evaluate);
+ }
break;
// Option value: &name
@@ -6871,6 +6875,202 @@ failret:
return OK;
}
+/// Get function arguments.
+static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,
+ int *varargs, int skip)
+{
+ bool mustend = false;
+ char_u *arg = *argp;
+ char_u *p = arg;
+ int c;
+ int i;
+
+ if (newargs != NULL) {
+ ga_init(newargs, (int)sizeof(char_u *), 3);
+ }
+
+ if (varargs != NULL) {
+ *varargs = false;
+ }
+
+ // Isolate the arguments: "arg1, arg2, ...)"
+ while (*p != endchar) {
+ if (p[0] == '.' && p[1] == '.' && p[2] == '.') {
+ if (varargs != NULL) {
+ *varargs = true;
+ }
+ p += 3;
+ mustend = true;
+ } else {
+ arg = p;
+ while (ASCII_ISALNUM(*p) || *p == '_') {
+ p++;
+ }
+ if (arg == p || isdigit(*arg)
+ || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
+ || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) {
+ if (!skip) {
+ EMSG2(_("E125: Illegal argument: %s"), arg);
+ }
+ break;
+ }
+ if (newargs != NULL) {
+ ga_grow(newargs, 1);
+ c = *p;
+ *p = NUL;
+ arg = vim_strsave(arg);
+ if (arg == NULL) {
+ goto err_ret;
+ }
+
+ // Check for duplicate argument name.
+ for (i = 0; i < newargs->ga_len; i++) {
+ if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) {
+ EMSG2(_("E853: Duplicate argument name: %s"), arg);
+ xfree(arg);
+ goto err_ret;
+ }
+ }
+ ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg;
+ newargs->ga_len++;
+
+ *p = c;
+ }
+ if (*p == ',') {
+ p++;
+ } else {
+ mustend = true;
+ }
+ }
+ p = skipwhite(p);
+ if (mustend && *p != endchar) {
+ if (!skip) {
+ EMSG2(_(e_invarg2), *argp);
+ }
+ break;
+ }
+ }
+ if (*p != endchar) {
+ goto err_ret;
+ }
+ p++; // skip "endchar"
+
+ *argp = p;
+ return OK;
+
+err_ret:
+ if (newargs != NULL) {
+ ga_clear_strings(newargs);
+ }
+ return FAIL;
+}
+
+/// Parse a lambda expression and get a Funcref from "*arg".
+/// Return OK or FAIL. Returns NOTDONE for dict or {expr}.
+static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
+{
+ garray_T newargs;
+ garray_T newlines;
+ ufunc_T *fp = NULL;
+ int varargs;
+ int ret;
+ char_u name[20];
+ char_u *start = skipwhite(*arg + 1);
+ char_u *s, *e;
+ static int lambda_no = 0;
+
+ // TODO(mike): What lengths should be used here?
+ ga_init(&newargs, (int)sizeof(char_u *), 80);
+ ga_init(&newlines, (int)sizeof(char_u *), 80);
+
+ // First, check if this is a lambda expression. "->" must exists.
+ ret = get_function_args(&start, '-', NULL, NULL, true);
+ if (ret == FAIL || *start != '>') {
+ return NOTDONE;
+ }
+
+ // Parse the arguments again.
+ *arg = skipwhite(*arg + 1);
+ ret = get_function_args(arg, '-', &newargs, &varargs, false);
+ if (ret == FAIL || **arg != '>') {
+ goto errret;
+ }
+
+ // Get the start and the end of the expression.
+ *arg = skipwhite(*arg + 1);
+ s = *arg;
+ ret = skip_expr(arg);
+ if (ret == FAIL) {
+ goto errret;
+ }
+ e = *arg;
+ *arg = skipwhite(*arg);
+ if (**arg != '}') {
+ goto errret;
+ }
+ (*arg)++;
+
+ if (evaluate) {
+ int len;
+ char_u *p;
+
+ fp = (ufunc_T *)xmalloc((unsigned)(sizeof(ufunc_T) + 20));
+ if (fp == NULL) {
+ goto errret;
+ }
+
+ snprintf((char *)name, sizeof(name), "<lambda>%d", lambda_no++);
+
+ ga_init(&newlines, (int)sizeof(char_u *), 1);
+ ga_grow(&newlines, 1);
+
+ // Add "return " before the expression.
+ // TODO(vim): Support multiple expressions.
+ len = 7 + e - s + 1;
+ p = (char_u *)xmalloc(len);
+ if (p == NULL) {
+ goto errret;
+ }
+ ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
+ STRCPY(p, "return ");
+ STRNCPY(p + 7, s, e - s);
+ p[7 + e - s] = NUL;
+
+ fp->uf_refcount = 1;
+ STRCPY(fp->uf_name, name);
+ hash_add(&func_hashtab, UF2HIKEY(fp));
+ fp->uf_args = newargs;
+ fp->uf_lines = newlines;
+
+#ifdef FEAT_PROFILE
+ fp->uf_tml_count = NULL;
+ fp->uf_tml_total = NULL;
+ fp->uf_tml_self = NULL;
+ fp->uf_profiling = false;
+ if (prof_def_func()) {
+ func_do_profile(fp);
+ }
+#endif
+ fp->uf_varargs = true;
+ fp->uf_flags = 0;
+ fp->uf_calls = 0;
+ fp->uf_script_ID = current_SID;
+
+ rettv->vval.v_string = vim_strsave(name);
+ rettv->v_type = VAR_FUNC;
+ } else {
+ ga_clear_strings(&newargs);
+ }
+
+ return OK;
+
+errret:
+ ga_clear_strings(&newargs);
+ ga_clear_strings(&newlines);
+ xfree(fp);
+ return FAIL;
+}
+
/// Convert the string to a floating point number
///
/// This uses strtod(). setlocale(LC_NUMERIC, "C") has been used earlier to
@@ -9448,7 +9648,7 @@ static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp)
goto theend;
}
- if (*s != NUL) { // check for trailing chars after expr
+ if (*s != NUL) { // check for trailing chars after expr
EMSG2(_(e_invexpr2), s);
goto theend;
}
@@ -20495,8 +20695,7 @@ void ex_function(exarg_T *eap)
char_u *line_arg = NULL;
garray_T newargs;
garray_T newlines;
- int varargs = FALSE;
- int mustend = FALSE;
+ int varargs = false;
int flags = 0;
ufunc_T *fp;
int indent;
@@ -20679,59 +20878,11 @@ void ex_function(exarg_T *eap)
EMSG(_("E862: Cannot use g: here"));
}
- /*
- * Isolate the arguments: "arg1, arg2, ...)"
- */
- while (*p != ')') {
- if (p[0] == '.' && p[1] == '.' && p[2] == '.') {
- varargs = TRUE;
- p += 3;
- mustend = TRUE;
- } else {
- arg = p;
- while (ASCII_ISALNUM(*p) || *p == '_')
- ++p;
- if (arg == p || isdigit(*arg)
- || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
- || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) {
- if (!eap->skip)
- EMSG2(_("E125: Illegal argument: %s"), arg);
- break;
- }
- ga_grow(&newargs, 1);
- c = *p;
- *p = NUL;
- arg = vim_strsave(arg);
-
- /* Check for duplicate argument name. */
- for (int i = 0; i < newargs.ga_len; ++i)
- if (STRCMP(((char_u **)(newargs.ga_data))[i], arg) == 0) {
- EMSG2(_("E853: Duplicate argument name: %s"), arg);
- xfree(arg);
- goto erret;
- }
-
- ((char_u **)(newargs.ga_data))[newargs.ga_len] = arg;
- *p = c;
- newargs.ga_len++;
- if (*p == ',')
- ++p;
- else
- mustend = TRUE;
- }
- p = skipwhite(p);
- if (mustend && *p != ')') {
- if (!eap->skip)
- EMSG2(_(e_invarg2), eap->arg);
- break;
- }
- }
- if (*p != ')') {
- goto erret;
+ if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) {
+ goto errret_2;
}
- ++p; // skip the ')'
- /* find extra arguments "range", "dict" and "abort" */
+ // find extra arguments "range", "dict" and "abort"
for (;; ) {
p = skipwhite(p);
if (STRNCMP(p, "range", 5) == 0) {
@@ -21041,6 +21192,7 @@ void ex_function(exarg_T *eap)
erret:
ga_clear_strings(&newargs);
+errret_2:
ga_clear_strings(&newlines);
ret_free:
xfree(skip_until);
@@ -21764,7 +21916,9 @@ void func_unref(char_u *name)
{
ufunc_T *fp;
- if (name != NULL && isdigit(*name)) {
+ if (name == NULL) {
+ return;
+ } else if (isdigit(*name)) {
fp = find_func(name);
if (fp == NULL) {
#ifdef EXITFREE
@@ -21777,6 +21931,16 @@ void func_unref(char_u *name)
} else {
user_func_unref(fp);
}
+ } else if (STRNCMP(name, "<lambda>", 8) == 0) {
+ // fail silently, when lambda function isn't found
+ fp = find_func(name);
+ if (fp != NULL && --fp->uf_refcount <= 0) {
+ // Only delete it when it's not being used. Otherwise it's done
+ // when "uf_calls" becomes zero.
+ if (fp->uf_calls == 0) {
+ func_free(fp);
+ }
+ }
}
}
@@ -21798,12 +21962,21 @@ void func_ref(char_u *name)
{
ufunc_T *fp;
- if (name != NULL && isdigit(*name)) {
+ if (name == NULL) {
+ return;
+ } else if (isdigit(*name)) {
fp = find_func(name);
- if (fp == NULL)
+ if (fp == NULL) {
EMSG2(_(e_intern2), "func_ref()");
- else
- ++fp->uf_refcount;
+ } else {
+ (fp->uf_refcount)++;
+ }
+ } else if (STRNCMP(name, "<lambda>", 8) == 0) {
+ // fail silently, when lambda function isn't found.
+ fp = find_func(name);
+ if (fp != NULL) {
+ (fp->uf_refcount)++;
+ }
}
}
@@ -21830,6 +22003,7 @@ call_user_func (
dictitem_T *v;
int fixvar_idx = 0; /* index in fixvar[] */
int ai;
+ bool islambda = false;
char_u numbuf[NUMBUFLEN];
char_u *name;
proftime_T wait_start;
@@ -21867,14 +22041,15 @@ call_user_func (
fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0);
fc->dbg_tick = debug_tick;
- /*
- * Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables
- * with names up to VAR_SHORT_LEN long. This avoids having to alloc/free
- * each argument variable and saves a lot of time.
- */
- /*
- * Init l: variables.
- */
+ if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0) {
+ islambda = true;
+ }
+
+ // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables
+ // with names up to VAR_SHORT_LEN long. This avoids having to alloc/free
+ // each argument variable and saves a lot of time.
+ //
+ // Init l: variables.
init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE);
if (selfdict != NULL) {
/* Set l:self to "selfdict". Use "name" to avoid a warning from
@@ -21916,31 +22091,49 @@ call_user_func (
fc->l_varlist.lv_refcount = DO_NOT_FREE_CNT;
fc->l_varlist.lv_lock = VAR_FIXED;
- /*
- * Set a:firstline to "firstline" and a:lastline to "lastline".
- * Set a:name to named arguments.
- * Set a:N to the "..." arguments.
- */
+ // Set a:firstline to "firstline" and a:lastline to "lastline".
+ // Set a:name to named arguments.
+ // Set a:N to the "..." arguments.
add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "firstline",
- (varnumber_T)firstline);
+ (varnumber_T)firstline);
add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "lastline",
- (varnumber_T)lastline);
- for (int i = 0; i < argcount; ++i) {
+ (varnumber_T)lastline);
+ for (int i = 0; i < argcount; i++) {
+ bool addlocal = false;
+ dictitem_T *v2;
+
ai = i - fp->uf_args.ga_len;
- if (ai < 0)
- /* named argument a:name */
+ if (ai < 0) {
+ // named argument a:name
name = FUNCARG(fp, i);
- else {
- /* "..." argument a:1, a:2, etc. */
- sprintf((char *)numbuf, "%d", ai + 1);
+ if (islambda) {
+ addlocal = true;
+ }
+ } else {
+ // "..." argument a:1, a:2, etc.
+ snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1);
name = numbuf;
}
if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) {
v = &fc->fixvar[fixvar_idx++].var;
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
+
+ if (addlocal) {
+ v2 = v;
+ }
} else {
v = xmalloc(sizeof(dictitem_T) + STRLEN(name));
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC;
+
+ if (addlocal) {
+ v2 = (dictitem_T *)xmalloc((unsigned)(sizeof(dictitem_T)
+ + STRLEN(name)));
+ if (v2 == NULL) {
+ xfree(v);
+ break;
+ }
+ v2->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC;
+ }
}
STRCPY(v->di_key, name);
hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v));
@@ -21950,6 +22143,15 @@ call_user_func (
v->di_tv = argvars[i];
v->di_tv.v_lock = VAR_FIXED;
+ // Named arguments can be accessed without the "a:" prefix in lambda
+ // expressions. Add to the l: dict.
+ if (addlocal) {
+ STRCPY(v2->di_key, name);
+ copy_tv(&v->di_tv, &v2->di_tv);
+ v2->di_tv.v_lock = VAR_FIXED;
+ hash_add(&fc->l_vars.dv_hashtab, DI2HIKEY(v2));
+ }
+
if (ai >= 0 && ai < MAX_FUNC_ARGS) {
list_append(&fc->l_varlist, &fc->l_listitems[ai]);
fc->l_listitems[ai].li_tv = argvars[i];
@@ -22167,7 +22369,9 @@ call_user_func (
copy_tv(&li->li_tv, &li->li_tv);
}
- if (--fp->uf_calls <= 0 && isdigit(*fp->uf_name) && fp->uf_refcount <= 0) {
+ if (--fp->uf_calls <= 0 && (isdigit(*fp->uf_name)
+ || STRNCMP(fp->uf_name, "<lambda>", 8) == 0)
+ && fp->uf_refcount <= 0) {
// Function was unreferenced while being used, free it now.
func_free(fp);
}