diff options
author | Michael Ennen <mike.ennen@gmail.com> | 2016-12-14 17:06:36 -0700 |
---|---|---|
committer | Michael Ennen <mike.ennen@gmail.com> | 2017-02-14 17:38:16 -0700 |
commit | bb7d0deb2f93bd7980a51dca05ae222e751ab632 (patch) | |
tree | e6b13f4f16dde3eb738cde39b2f3b8db86623eb2 /src/nvim/eval.c | |
parent | 6c423989fc5becb294dacedceaac0c2e878a3858 (diff) | |
download | rneovim-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.c | 368 |
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); } |