patch 8.1.1803: all builtin functions are global
Problem: All builtin functions are global.
Solution: Add the method call operator ->. Implemented for a limited number
of functions.
diff --git a/src/eval.c b/src/eval.c
index 6d7bd66..7875ede 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -4412,6 +4412,7 @@
* + in front unary plus (ignored)
* trailing [] subscript in String or List
* trailing .name entry in Dictionary
+ * trailing ->name() method call
*
* "arg" must point to the first non-white of the expression.
* "arg" is advanced to the next non-white after the recognized expression.
@@ -4690,13 +4691,12 @@
funcexe_T funcexe;
// Invoke the function.
- funcexe.argv_func = NULL;
+ vim_memset(&funcexe, 0, sizeof(funcexe));
funcexe.firstline = curwin->w_cursor.lnum;
funcexe.lastline = curwin->w_cursor.lnum;
funcexe.doesrange = &len;
funcexe.evaluate = evaluate;
funcexe.partial = partial;
- funcexe.selfdict = NULL;
ret = get_func_tv(s, len, rettv, arg, &funcexe);
}
vim_free(s);
@@ -4802,6 +4802,70 @@
}
/*
+ * Evaluate "->method()".
+ * "*arg" points to the '-'.
+ * Returns FAIL or OK. "*arg" is advanced to after the ')'.
+ */
+ static int
+eval_method(
+ char_u **arg,
+ typval_T *rettv,
+ int evaluate,
+ int verbose) /* give error messages */
+{
+ char_u *name;
+ long len;
+ funcexe_T funcexe;
+ int ret = OK;
+ typval_T base = *rettv;
+
+ // Skip over the ->.
+ *arg += 2;
+
+ // Locate the method name.
+ name = *arg;
+ for (len = 0; ASCII_ISALNUM(name[len]) || name[len] == '_'; ++len)
+ ;
+ if (len == 0)
+ {
+ if (verbose)
+ emsg(_("E260: Missing name after ->"));
+ return FAIL;
+ }
+
+ // Check for the "(". Skip over white space after it.
+ if (name[len] != '(')
+ {
+ if (verbose)
+ semsg(_(e_missingparen), name);
+ return FAIL;
+ }
+ *arg += len;
+
+ vim_memset(&funcexe, 0, sizeof(funcexe));
+ funcexe.evaluate = evaluate;
+ funcexe.basetv = &base;
+ rettv->v_type = VAR_UNKNOWN;
+ ret = get_func_tv(name, len, rettv, arg, &funcexe);
+
+ /* Clear the funcref afterwards, so that deleting it while
+ * evaluating the arguments is possible (see test55). */
+ if (evaluate)
+ clear_tv(&base);
+
+ /* Stop the expression evaluation when immediately aborting on
+ * error, or when an interrupt occurred or an exception was thrown
+ * but not caught. */
+ if (aborting())
+ {
+ if (ret == OK)
+ clear_tv(rettv);
+ ret = FAIL;
+ }
+ return ret;
+}
+
+/*
* Evaluate an "[expr]" or "[expr:expr]" index. Also "dict.key".
* "*arg" points to the '[' or '.'.
* Returns FAIL or OK. "*arg" is advanced to after the ']'.
@@ -7359,9 +7423,13 @@
}
/*
- * Handle expr[expr], expr[expr:expr] subscript and .name lookup.
- * Also handle function call with Funcref variable: func(expr)
- * Can all be combined: dict.func(expr)[idx]['func'](expr)
+ * Handle:
+ * - expr[expr], expr[expr:expr] subscript
+ * - ".name" lookup
+ * - function call with Funcref variable: func(expr)
+ * - method call: var->method()
+ *
+ * Can all be combined in any order: dict.func(expr)[idx]['func'](expr)->len()
*/
int
handle_subscript(
@@ -7378,14 +7446,15 @@
// "." is ".name" lookup when we found a dict or when evaluating and
// scriptversion is at least 2, where string concatenation is "..".
while (ret == OK
- && (**arg == '['
- || (**arg == '.' && (rettv->v_type == VAR_DICT
+ && (((**arg == '['
+ || (**arg == '.' && (rettv->v_type == VAR_DICT
|| (!evaluate
&& (*arg)[1] != '.'
&& current_sctx.sc_version >= 2)))
- || (**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC
+ || (**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC
|| rettv->v_type == VAR_PARTIAL)))
- && !VIM_ISWHITE(*(*arg - 1)))
+ && !VIM_ISWHITE(*(*arg - 1)))
+ || (**arg == '-' && (*arg)[1] == '>')))
{
if (**arg == '(')
{
@@ -7410,10 +7479,9 @@
else
s = (char_u *)"";
- funcexe.argv_func = NULL;
+ vim_memset(&funcexe, 0, sizeof(funcexe));
funcexe.firstline = curwin->w_cursor.lnum;
funcexe.lastline = curwin->w_cursor.lnum;
- funcexe.doesrange = NULL;
funcexe.evaluate = evaluate;
funcexe.partial = pt;
funcexe.selfdict = selfdict;
@@ -7436,6 +7504,14 @@
dict_unref(selfdict);
selfdict = NULL;
}
+ else if (**arg == '-')
+ {
+ if (eval_method(arg, rettv, evaluate, verbose) == FAIL)
+ {
+ clear_tv(rettv);
+ ret = FAIL;
+ }
+ }
else /* **arg == '[' || **arg == '.' */
{
dict_unref(selfdict);
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 54fc2f5..7213e0d 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -412,14 +412,16 @@
* Array with names and number of arguments of all internal functions
* MUST BE KEPT SORTED IN strcmp() ORDER FOR BINARY SEARCH!
*/
-static struct fst
+typedef struct
{
char *f_name; /* function name */
char f_min_argc; /* minimal number of arguments */
char f_max_argc; /* maximal number of arguments */
void (*f_func)(typval_T *args, typval_T *rvar);
/* implementation of function */
-} functions[] =
+} funcentry_T;
+
+static funcentry_T global_functions[] =
{
#ifdef FEAT_FLOAT
{"abs", 1, 1, f_abs},
@@ -987,6 +989,37 @@
{"xor", 2, 2, f_xor},
};
+/*
+ * Methods that call the internal function with the base as the first argument.
+ */
+static funcentry_T base_methods[] =
+{
+ {"add", 1, 1, f_add},
+ {"copy", 0, 0, f_copy},
+ {"count", 1, 3, f_count},
+ {"empty", 0, 0, f_empty},
+ {"extend", 1, 2, f_extend},
+ {"filter", 1, 1, f_filter},
+ {"get", 1, 2, f_get},
+ {"index", 1, 3, f_index},
+ {"insert", 1, 2, f_insert},
+ {"items", 0, 0, f_items},
+ {"join", 0, 1, f_join},
+ {"keys", 0, 0, f_keys},
+ {"len", 0, 0, f_len},
+ {"map", 1, 1, f_map},
+ {"max", 0, 0, f_max},
+ {"min", 0, 0, f_min},
+ {"remove", 1, 2, f_remove},
+ {"repeat", 1, 1, f_repeat},
+ {"reverse", 0, 0, f_reverse},
+ {"sort", 0, 2, f_sort},
+ {"string", 0, 0, f_string},
+ {"type", 0, 0, f_type},
+ {"uniq", 0, 2, f_uniq},
+ {"values", 0, 0, f_values},
+};
+
#if defined(FEAT_CMDL_COMPL) || defined(PROTO)
/*
@@ -1007,11 +1040,11 @@
if (name != NULL)
return name;
}
- if (++intidx < (int)(sizeof(functions) / sizeof(struct fst)))
+ if (++intidx < (int)(sizeof(global_functions) / sizeof(funcentry_T)))
{
- STRCPY(IObuff, functions[intidx].f_name);
+ STRCPY(IObuff, global_functions[intidx].f_name);
STRCAT(IObuff, "(");
- if (functions[intidx].f_max_argc == 0)
+ if (global_functions[intidx].f_max_argc == 0)
STRCAT(IObuff, ")");
return IObuff;
}
@@ -1043,21 +1076,25 @@
#endif /* FEAT_CMDL_COMPL */
/*
- * Find internal function in table above.
+ * Find internal function in table "functions".
* Return index, or -1 if not found
*/
- int
+ static int
find_internal_func(
- char_u *name) /* name of the function */
+ char_u *name, // name of the function
+ funcentry_T *functions) // functions table to use
{
int first = 0;
- int last = (int)(sizeof(functions) / sizeof(struct fst)) - 1;
+ int last;
int cmp;
int x;
- /*
- * Find the function name in the table. Binary search.
- */
+ if (functions == global_functions)
+ last = (int)(sizeof(global_functions) / sizeof(funcentry_T)) - 1;
+ else
+ last = (int)(sizeof(base_methods) / sizeof(funcentry_T)) - 1;
+
+ // Find the function name in the table. Binary search.
while (first <= last)
{
x = first + ((unsigned)(last - first) >> 1);
@@ -1073,6 +1110,12 @@
}
int
+has_internal_func(char_u *name)
+{
+ return find_internal_func(name, global_functions) >= 0;
+}
+
+ int
call_internal_func(
char_u *name,
int argcount,
@@ -1081,15 +1124,47 @@
{
int i;
- i = find_internal_func(name);
+ i = find_internal_func(name, global_functions);
if (i < 0)
return ERROR_UNKNOWN;
- if (argcount < functions[i].f_min_argc)
+ if (argcount < global_functions[i].f_min_argc)
return ERROR_TOOFEW;
- if (argcount > functions[i].f_max_argc)
+ if (argcount > global_functions[i].f_max_argc)
return ERROR_TOOMANY;
argvars[argcount].v_type = VAR_UNKNOWN;
- functions[i].f_func(argvars, rettv);
+ global_functions[i].f_func(argvars, rettv);
+ return ERROR_NONE;
+}
+
+/*
+ * Invoke a method for base->method().
+ */
+ int
+call_internal_method(
+ char_u *name,
+ int argcount,
+ typval_T *argvars,
+ typval_T *rettv,
+ typval_T *basetv)
+{
+ int i;
+ int fi;
+ typval_T argv[MAX_FUNC_ARGS + 1];
+
+ fi = find_internal_func(name, base_methods);
+ if (fi < 0)
+ return ERROR_UNKNOWN;
+ if (argcount < base_methods[fi].f_min_argc)
+ return ERROR_TOOFEW;
+ if (argcount > base_methods[fi].f_max_argc)
+ return ERROR_TOOMANY;
+
+ argv[0] = *basetv;
+ for (i = 0; i < argcount; ++i)
+ argv[i + 1] = argvars[i];
+ argv[argcount + 1].v_type = VAR_UNKNOWN;
+
+ base_methods[fi].f_func(argv, rettv);
return ERROR_NONE;
}
diff --git a/src/globals.h b/src/globals.h
index 2d64686..6faee3b 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -1592,6 +1592,7 @@
EXTERN char e_zerocount[] INIT(= N_("E939: Positive count required"));
#ifdef FEAT_EVAL
EXTERN char e_usingsid[] INIT(= N_("E81: Using <SID> not in a script context"));
+EXTERN char e_missingparen[] INIT(= N_("E107: Missing parentheses: %s"));
#endif
#ifdef FEAT_CLIENTSERVER
EXTERN char e_invexprmsg[] INIT(= N_("E449: Invalid expression received"));
diff --git a/src/proto/evalfunc.pro b/src/proto/evalfunc.pro
index afd5f63..932193d 100644
--- a/src/proto/evalfunc.pro
+++ b/src/proto/evalfunc.pro
@@ -1,8 +1,9 @@
/* evalfunc.c */
char_u *get_function_name(expand_T *xp, int idx);
char_u *get_expr_name(expand_T *xp, int idx);
-int find_internal_func(char_u *name);
+int has_internal_func(char_u *name);
int call_internal_func(char_u *name, int argcount, typval_T *argvars, typval_T *rettv);
+int call_internal_method(char_u *name, int argcount, typval_T *argvars, typval_T *rettv, typval_T *basetv);
linenr_T tv_get_lnum(typval_T *argvars);
buf_T *buflist_find_by_name(char_u *name, int curtab_only);
buf_T *tv_get_buf(typval_T *tv, int curtab_only);
diff --git a/src/structs.h b/src/structs.h
index 73cacef..c34bbeb 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1619,6 +1619,7 @@
int 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;
struct partial_S
diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak
index a0898dc..b3abcba 100644
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -180,6 +180,7 @@
test_matchadd_conceal \
test_matchadd_conceal_utf8 \
test_memory_usage \
+ test_method \
test_menu \
test_messages \
test_mksession \
@@ -373,6 +374,7 @@
test_marks.res \
test_matchadd_conceal.res \
test_memory_usage.res \
+ test_method.res \
test_mksession.res \
test_nested_function.res \
test_netbeans.res \
diff --git a/src/testdir/test_method.vim b/src/testdir/test_method.vim
new file mode 100644
index 0000000..e0631e6
--- /dev/null
+++ b/src/testdir/test_method.vim
@@ -0,0 +1,61 @@
+" Tests for ->method()
+
+func Test_list()
+ let l = [1, 2, 3]
+ call assert_equal([1, 2, 3, 4], [1, 2, 3]->add(4))
+ call assert_equal(l, l->copy())
+ call assert_equal(1, l->count(2))
+ call assert_false(l->empty())
+ call assert_true([]->empty())
+ call assert_equal([1, 2, 3, 4, 5], [1, 2, 3]->extend([4, 5]))
+ call assert_equal([1, 3], [1, 2, 3]->filter('v:val != 2'))
+ call assert_equal(2, l->get(1))
+ call assert_equal(1, l->index(2))
+ call assert_equal([0, 1, 2, 3], [1, 2, 3]->insert(0))
+ call assert_fails('let x = l->items()', 'E715:')
+ call assert_equal('1 2 3', l->join())
+ call assert_fails('let x = l->keys()', 'E715:')
+ call assert_equal(3, l->len())
+ call assert_equal([2, 3, 4], [1, 2, 3]->map('v:val + 1'))
+ call assert_equal(3, l->max())
+ call assert_equal(1, l->min())
+ call assert_equal(2, [1, 2, 3]->remove(1))
+ call assert_equal([1, 2, 3, 1, 2, 3], l->repeat(2))
+ call assert_equal([3, 2, 1], [1, 2, 3]->reverse())
+ call assert_equal([1, 2, 3, 4], [4, 2, 3, 1]->sort())
+ call assert_equal('[1, 2, 3]', l->string())
+ call assert_equal(v:t_list, l->type())
+ call assert_equal([1, 2, 3], [1, 1, 2, 3, 3]->uniq())
+ call assert_fails('let x = l->values()', 'E715:')
+endfunc
+
+func Test_dict()
+ let d = #{one: 1, two: 2, three: 3}
+
+ call assert_equal(d, d->copy())
+ call assert_equal(1, d->count(2))
+ call assert_false(d->empty())
+ call assert_true({}->empty())
+ call assert_equal(#{one: 1, two: 2, three: 3, four: 4}, d->extend(#{four: 4}))
+ call assert_equal(#{one: 1, two: 2, three: 3}, d->filter('v:val != 4'))
+ call assert_equal(2, d->get('two'))
+ call assert_fails("let x = d->index(2)", 'E897:')
+ call assert_fails("let x = d->insert(0)", 'E899:')
+ call assert_equal([['one', 1], ['two', 2], ['three', 3]], d->items())
+ call assert_fails("let x = d->join()", 'E714:')
+ call assert_equal(['one', 'two', 'three'], d->keys())
+ call assert_equal(3, d->len())
+ call assert_equal(#{one: 2, two: 3, three: 4}, d->map('v:val + 1'))
+ call assert_equal(#{one: 1, two: 2, three: 3}, d->map('v:val - 1'))
+ call assert_equal(3, d->max())
+ call assert_equal(1, d->min())
+ call assert_equal(2, d->remove("two"))
+ let d.two = 2
+ call assert_fails('let x = d->repeat(2)', 'E731:')
+ call assert_fails('let x = d->reverse()', 'E899:')
+ call assert_fails('let x = d->sort()', 'E686:')
+ call assert_equal("{'one': 1, 'two': 2, 'three': 3}", d->string())
+ call assert_equal(v:t_dict, d->type())
+ call assert_fails('let x = d->uniq()', 'E686:')
+ call assert_equal([1, 2, 3], d->values())
+endfunc
diff --git a/src/userfunc.c b/src/userfunc.c
index 9d2063b..8e834a3 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -1431,10 +1431,9 @@
{
funcexe_T funcexe;
- funcexe.argv_func = NULL;
+ vim_memset(&funcexe, 0, sizeof(funcexe));
funcexe.firstline = curwin->w_cursor.lnum;
funcexe.lastline = curwin->w_cursor.lnum;
- funcexe.doesrange = NULL;
funcexe.evaluate = TRUE;
funcexe.partial = partial;
funcexe.selfdict = selfdict;
@@ -1555,7 +1554,10 @@
/*
* User defined function.
*/
- if (partial != NULL && partial->pt_func != NULL)
+ if (funcexe->basetv != NULL)
+ // TODO: support User function: base->Method()
+ fp = NULL;
+ else if (partial != NULL && partial->pt_func != NULL)
fp = partial->pt_func;
else
fp = find_func(rfname);
@@ -1625,6 +1627,14 @@
}
}
}
+ else if (funcexe->basetv != NULL)
+ {
+ /*
+ * Find the method name in the table, call its implementation.
+ */
+ error = call_internal_method(fname, argcount, argvars, rettv,
+ funcexe->basetv);
+ }
else
{
/*
@@ -2715,7 +2725,7 @@
translated_function_exists(char_u *name)
{
if (builtin_function(name, -1))
- return find_internal_func(name) >= 0;
+ return has_internal_func(name);
return find_func(name) != NULL;
}
@@ -3084,7 +3094,7 @@
if (*startarg != '(')
{
- semsg(_("E107: Missing parentheses: %s"), eap->arg);
+ semsg(_(e_missingparen), eap->arg);
goto end;
}
@@ -3120,7 +3130,7 @@
}
arg = startarg;
- funcexe.argv_func = NULL;
+ vim_memset(&funcexe, 0, sizeof(funcexe));
funcexe.firstline = eap->line1;
funcexe.lastline = eap->line2;
funcexe.doesrange = &doesrange;
diff --git a/src/version.c b/src/version.c
index 6268b8e..5e027dd 100644
--- a/src/version.c
+++ b/src/version.c
@@ -774,6 +774,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1803,
+/**/
1802,
/**/
1801,