aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/eval
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2021-08-26 04:26:32 -0700
committerGitHub <noreply@github.com>2021-08-26 04:26:32 -0700
commit4c499899b2c3b31d57c1911c522683bdb2e32a0a (patch)
tree73bd8630718e854355ae1cc9c4272838618b67c9 /src/nvim/eval
parent2548a9e18037339c4c502d971bdeaf909b82a739 (diff)
parentb2994e35c9357a8144beaf27e1a8ea4dd133f5d4 (diff)
downloadrneovim-4c499899b2c3b31d57c1911c522683bdb2e32a0a.tar.gz
rneovim-4c499899b2c3b31d57c1911c522683bdb2e32a0a.tar.bz2
rneovim-4c499899b2c3b31d57c1911c522683bdb2e32a0a.zip
Merge #15293 Vimscript "method" syntax
Port VimL's method call syntax - vim-patch:8.1.{1638,1800,1803,1807,1809,1816,1820,1821,1828,1834,1835,1861,1863,1878,1879,1888,1909,1911,1912}
Diffstat (limited to 'src/nvim/eval')
-rw-r--r--src/nvim/eval/funcs.c57
-rw-r--r--src/nvim/eval/funcs.h5
-rw-r--r--src/nvim/eval/userfunc.c139
-rw-r--r--src/nvim/eval/userfunc.h26
4 files changed, 164 insertions, 63 deletions
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 4e409cca50..8a1258efd4 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -175,6 +175,53 @@ const VimLFuncDef *find_internal_func(const char *const name)
return find_internal_func_gperf(name, len);
}
+int call_internal_func(const char_u *const fname, const int argcount,
+ typval_T *const argvars, typval_T *const rettv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const VimLFuncDef *const fdef = find_internal_func((const char *)fname);
+ if (fdef == NULL) {
+ return ERROR_UNKNOWN;
+ } else if (argcount < fdef->min_argc) {
+ return ERROR_TOOFEW;
+ } else if (argcount > fdef->max_argc) {
+ return ERROR_TOOMANY;
+ }
+ argvars[argcount].v_type = VAR_UNKNOWN;
+ fdef->func(argvars, rettv, fdef->data);
+ return ERROR_NONE;
+}
+
+/// Invoke a method for base->method().
+int call_internal_method(const char_u *const fname, const int argcount,
+ typval_T *const argvars, typval_T *const rettv,
+ typval_T *const basetv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const VimLFuncDef *const fdef = find_internal_func((const char *)fname);
+ if (fdef == NULL) {
+ return ERROR_UNKNOWN;
+ } else if (fdef->base_arg == BASE_NONE) {
+ return ERROR_NOTMETHOD;
+ } else if (argcount + 1 < fdef->min_argc) {
+ return ERROR_TOOFEW;
+ } else if (argcount + 1 > fdef->max_argc) {
+ return ERROR_TOOMANY;
+ }
+
+ typval_T argv[MAX_FUNC_ARGS + 1];
+ const ptrdiff_t base_index
+ = fdef->base_arg == BASE_LAST ? argcount : fdef->base_arg - 1;
+ memcpy(argv, argvars, base_index * sizeof(typval_T));
+ argv[base_index] = *basetv;
+ memcpy(argv + base_index + 1, argvars + base_index,
+ (argcount - base_index) * sizeof(typval_T));
+ argv[argcount + 1].v_type = VAR_UNKNOWN;
+
+ fdef->func(argv, rettv, fdef->data);
+ return ERROR_NONE;
+}
+
/*
* Return TRUE for a non-zero Number and a non-empty String.
*/
@@ -9420,7 +9467,6 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)
int res;
typval_T rettv;
typval_T argv[3];
- int dummy;
const char *func_name;
partial_T *partial = sortinfo->item_compare_partial;
@@ -9444,10 +9490,11 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)
tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]);
rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this
- res = call_func((const char_u *)func_name,
- -1,
- &rettv, 2, argv, NULL, 0L, 0L, &dummy, true,
- partial, sortinfo->item_compare_selfdict);
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.evaluate = true;
+ funcexe.partial = partial;
+ funcexe.selfdict = sortinfo->item_compare_selfdict;
+ res = call_func((const char_u *)func_name, -1, &rettv, 2, argv, &funcexe);
tv_clear(&argv[0]);
tv_clear(&argv[1]);
diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h
index a343290734..c6a0cb959e 100644
--- a/src/nvim/eval/funcs.h
+++ b/src/nvim/eval/funcs.h
@@ -9,11 +9,16 @@ typedef void (*FunPtr)(void);
/// Prototype of C function that implements VimL function
typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data);
+/// Special flags for base_arg @see VimLFuncDef
+#define BASE_NONE 0 ///< Not a method (no base argument).
+#define BASE_LAST UINT8_MAX ///< Use the last argument as the method base.
+
/// Structure holding VimL function definition
typedef struct fst {
char *name; ///< Name of the function.
uint8_t min_argc; ///< Minimal number of arguments.
uint8_t max_argc; ///< Maximal number of arguments.
+ uint8_t base_arg; ///< Method base arg # (1-indexed), BASE_NONE or BASE_LAST.
VimLFunc func; ///< Function implementation.
FunPtr data; ///< Userdata for function implementation.
} VimLFuncDef;
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index deddec413b..4184e4d922 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -414,12 +414,7 @@ get_func_tv(
int len, // length of "name" or -1 to use strlen()
typval_T *rettv,
char_u **arg, // argument, pointing to the '('
- linenr_T firstline, // first line of range
- linenr_T lastline, // last line of range
- int *doesrange, // return: function handled range
- int evaluate,
- partial_T *partial, // for extra arguments
- dict_T *selfdict // Dictionary for "self"
+ funcexe_T *funcexe // various values
)
{
char_u *argp;
@@ -431,12 +426,13 @@ get_func_tv(
* Get the arguments.
*/
argp = *arg;
- while (argcount < MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) {
+ while (argcount < MAX_FUNC_ARGS
+ - (funcexe->partial == NULL ? 0 : funcexe->partial->pt_argc)) {
argp = skipwhite(argp + 1); // skip the '(' or ','
if (*argp == ')' || *argp == ',' || *argp == NUL) {
break;
}
- if (eval1(&argp, &argvars[argcount], evaluate) == FAIL) {
+ if (eval1(&argp, &argvars[argcount], funcexe->evaluate) == FAIL) {
ret = FAIL;
break;
}
@@ -463,9 +459,7 @@ get_func_tv(
((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i];
}
}
- ret = call_func(name, len, rettv, argcount, argvars, NULL,
- firstline, lastline, doesrange, evaluate,
- partial, selfdict);
+ ret = call_func(name, len, rettv, argcount, argvars, funcexe);
funcargs.ga_len -= i;
} else if (!aborting()) {
@@ -1367,7 +1361,6 @@ int func_call(char_u *name, typval_T *args, partial_T *partial,
{
typval_T argv[MAX_FUNC_ARGS + 1];
int argc = 0;
- int dummy;
int r = 0;
TV_LIST_ITER(args->vval.v_list, item, {
@@ -1380,9 +1373,13 @@ int func_call(char_u *name, typval_T *args, partial_T *partial,
tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]);
});
- r = call_func(name, -1, rettv, argc, argv, NULL,
- curwin->w_cursor.lnum, curwin->w_cursor.lnum,
- &dummy, true, partial, selfdict);
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.firstline = curwin->w_cursor.lnum;
+ funcexe.lastline = curwin->w_cursor.lnum;
+ funcexe.evaluate = true;
+ funcexe.partial = partial;
+ funcexe.selfdict = selfdict;
+ r = call_func(name, -1, rettv, argc, argv, &funcexe);
func_call_skip_call:
// Free the arguments.
@@ -1402,6 +1399,9 @@ static void user_func_error(int error, const char_u *name)
case ERROR_UNKNOWN:
emsg_funcname(N_("E117: Unknown function: %s"), name);
break;
+ case ERROR_NOTMETHOD:
+ emsg_funcname(N_("E276: Cannot use function as a method: %s"), name);
+ break;
case ERROR_DELETED:
emsg_funcname(N_("E933: Function was deleted: %s"), name);
break;
@@ -1423,12 +1423,25 @@ static void user_func_error(int error, const char_u *name)
}
}
+/// Used by call_func to add a method base (if any) to a function argument list
+/// as the first argument. @see call_func
+static void argv_add_base(typval_T *const basetv, typval_T **const argvars,
+ int *const argcount, typval_T *const new_argvars,
+ int *const argv_base)
+ FUNC_ATTR_NONNULL_ARG(2, 3, 4, 5)
+{
+ if (basetv != NULL) {
+ // Method call: base->Method()
+ memmove(&new_argvars[1], *argvars, sizeof(typval_T) * (*argcount));
+ new_argvars[0] = *basetv;
+ (*argcount)++;
+ *argvars = new_argvars;
+ *argv_base = 1;
+ }
+}
+
/// Call a function with its resolved parameters
///
-/// "argv_func", when not NULL, can be used to fill in arguments only when the
-/// invoked function uses them. It is called like this:
-/// new_argcount = argv_func(current_argcount, argv, called_func_argcount)
-///
/// @return FAIL if function cannot be called, else OK (even if an error
/// occurred while executing the function! Set `msg_list` to capture
/// the error, see do_cmdline()).
@@ -1440,15 +1453,9 @@ call_func(
int argcount_in, // number of "argvars"
typval_T *argvars_in, // vars for arguments, must have "argcount"
// PLUS ONE elements!
- ArgvFunc argv_func, // function to fill in argvars
- linenr_T firstline, // first line of range
- linenr_T lastline, // last line of range
- int *doesrange, // [out] function handled range
- bool evaluate,
- partial_T *partial, // optional, can be NULL
- dict_T *selfdict_in // Dictionary for "self"
+ funcexe_T *funcexe // more arguments
)
- FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9)
+ FUNC_ATTR_NONNULL_ARG(1, 3, 5, 6)
{
int ret = FAIL;
int error = ERROR_NONE;
@@ -1459,9 +1466,12 @@ call_func(
char_u *name = NULL;
int argcount = argcount_in;
typval_T *argvars = argvars_in;
- dict_T *selfdict = selfdict_in;
- typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" is not NULL
+ dict_T *selfdict = funcexe->selfdict;
+ typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" or
+ // "funcexe->basetv" is not NULL
int argv_clear = 0;
+ int argv_base = 0;
+ partial_T *partial = funcexe->partial;
// Initialize rettv so that it is safe for caller to invoke clear_tv(rettv)
// even when call_func() returns FAIL.
@@ -1480,14 +1490,15 @@ call_func(
fname = fname_trans_sid(name, fname_buf, &tofree, &error);
}
- *doesrange = false;
+ if (funcexe->doesrange != NULL) {
+ *funcexe->doesrange = false;
+ }
if (partial != NULL) {
// When the function has a partial with a dict and there is a dict
// argument, use the dict argument. That is backwards compatible.
// When the dict was bound explicitly use the one from the partial.
- if (partial->pt_dict != NULL
- && (selfdict_in == NULL || !partial->pt_auto)) {
+ if (partial->pt_dict != NULL && (selfdict == NULL || !partial->pt_auto)) {
selfdict = partial->pt_dict;
}
if (error == ERROR_NONE && partial->pt_argc > 0) {
@@ -1506,7 +1517,7 @@ call_func(
}
}
- if (error == ERROR_NONE && evaluate) {
+ if (error == ERROR_NONE && funcexe->evaluate) {
char_u *rfname = fname;
// Ignore "g:" before a function name.
@@ -1521,7 +1532,12 @@ call_func(
if (is_luafunc(partial)) {
if (len > 0) {
error = ERROR_NONE;
+ argv_add_base(funcexe->basetv, &argvars, &argcount, argv, &argv_base);
nlua_typval_call((const char *)funcname, len, argvars, argcount, rettv);
+ } else {
+ // v:lua was called directly; show its name in the emsg
+ XFREE_CLEAR(name);
+ funcname = (const char_u *)"v:lua";
}
} else if (fp != NULL || !builtin_function((const char *)rfname, -1)) {
// User defined function.
@@ -1549,13 +1565,16 @@ call_func(
cfunc_T cb = fp->uf_cb;
error = (*cb)(argcount, argvars, rettv, fp->uf_cb_state);
} else if (fp != NULL) {
- if (argv_func != NULL) {
+ if (funcexe->argv_func != NULL) {
// postponed filling in the arguments, do it now
- argcount = argv_func(argcount, argvars, argv_clear,
- fp->uf_args.ga_len);
+ argcount = funcexe->argv_func(argcount, argvars, argv_clear,
+ fp->uf_args.ga_len);
}
- if (fp->uf_flags & FC_RANGE) {
- *doesrange = true;
+
+ argv_add_base(funcexe->basetv, &argvars, &argcount, argv, &argv_base);
+
+ if (fp->uf_flags & FC_RANGE && funcexe->doesrange != NULL) {
+ *funcexe->doesrange = true;
}
if (argcount < fp->uf_args.ga_len - fp->uf_def_args.ga_len) {
error = ERROR_TOOFEW;
@@ -1565,25 +1584,20 @@ call_func(
error = ERROR_DICT;
} else {
// Call the user function.
- call_user_func(fp, argcount, argvars, rettv, firstline, lastline,
+ call_user_func(fp, argcount, argvars, rettv, funcexe->firstline,
+ funcexe->lastline,
(fp->uf_flags & FC_DICT) ? selfdict : NULL);
error = ERROR_NONE;
}
}
+ } else if (funcexe->basetv != NULL) {
+ // expr->method(): Find the method name in the table, call its
+ // implementation with the base as one of the arguments.
+ error = call_internal_method(fname, argcount, argvars, rettv,
+ funcexe->basetv);
} else {
// Find the function name in the table, call its implementation.
- const VimLFuncDef *const fdef = find_internal_func((const char *)fname);
- if (fdef != NULL) {
- if (argcount < fdef->min_argc) {
- error = ERROR_TOOFEW;
- } else if (argcount > fdef->max_argc) {
- error = ERROR_TOOMANY;
- } else {
- argvars[argcount].v_type = VAR_UNKNOWN;
- fdef->func(argvars, rettv, fdef->data);
- error = ERROR_NONE;
- }
- }
+ error = call_internal_func(fname, argcount, argvars, rettv);
}
/*
* The function call (or "FuncUndefined" autocommand sequence) might
@@ -1607,9 +1621,11 @@ theend:
user_func_error(error, (name != NULL) ? name : funcname);
}
+ // clear the copies made from the partial
while (argv_clear > 0) {
- tv_clear(&argv[--argv_clear]);
+ tv_clear(&argv[--argv_clear + argv_base]);
}
+
xfree(tofree);
xfree(name);
@@ -2901,7 +2917,7 @@ void ex_call(exarg_T *eap)
int len;
typval_T rettv;
linenr_T lnum;
- int doesrange;
+ bool doesrange;
bool failed = false;
funcdict_T fudi;
partial_T *partial = NULL;
@@ -2947,7 +2963,7 @@ void ex_call(exarg_T *eap)
rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this.
if (*startarg != '(') {
- EMSG2(_("E107: Missing parentheses: %s"), eap->arg);
+ EMSG2(_(e_missingparen), eap->arg);
goto end;
}
@@ -2965,15 +2981,22 @@ void ex_call(exarg_T *eap)
curwin->w_cursor.coladd = 0;
}
arg = startarg;
- if (get_func_tv(name, -1, &rettv, &arg,
- eap->line1, eap->line2, &doesrange,
- true, partial, fudi.fd_dict) == FAIL) {
+
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.firstline = eap->line1;
+ funcexe.lastline = eap->line2;
+ funcexe.doesrange = &doesrange;
+ funcexe.evaluate = true;
+ funcexe.partial = partial;
+ funcexe.selfdict = fudi.fd_dict;
+ if (get_func_tv(name, -1, &rettv, &arg, &funcexe) == FAIL) {
failed = true;
break;
}
// Handle a function returning a Funcref, Dictionary or List.
- if (handle_subscript((const char **)&arg, &rettv, true, true)
+ if (handle_subscript((const char **)&arg, &rettv, true, true,
+ (const char_u *)name, (const char_u **)&name)
== FAIL) {
failed = true;
break;
diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h
index e8ad0bf1da..3f111343d2 100644
--- a/src/nvim/eval/userfunc.h
+++ b/src/nvim/eval/userfunc.h
@@ -28,11 +28,37 @@ typedef enum {
ERROR_OTHER,
ERROR_BOTH,
ERROR_DELETED,
+ ERROR_NOTMETHOD,
} FnameTransError;
+/// Used in funcexe_T. Returns the new argcount.
typedef int (*ArgvFunc)(int current_argcount, typval_T *argv, int argskip,
int called_func_argcount);
+/// Structure passed between functions dealing with function call execution.
+typedef struct {
+ ArgvFunc argv_func; ///< when not NULL, can be used to fill in arguments only
+ ///< when the invoked function uses them
+ linenr_T firstline; ///< first line of range
+ linenr_T lastline; ///< last line of range
+ bool *doesrange; ///< [out] if not NULL: function handled range
+ bool evaluate; ///< actually evaluate expressions
+ partial_T *partial; ///< for extra arguments
+ dict_T *selfdict; ///< Dictionary for "self"
+ typval_T *basetv; ///< base for base->method()
+} funcexe_T;
+
+#define FUNCEXE_INIT (funcexe_T) { \
+ .argv_func = NULL, \
+ .firstline = 0, \
+ .lastline = 0, \
+ .doesrange = NULL, \
+ .evaluate = false, \
+ .partial = NULL, \
+ .selfdict = NULL, \
+ .basetv = NULL, \
+}
+
#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j]
#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j]