1
0
forked from aniani/vim

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.
This commit is contained in:
Bram Moolenaar 2019-08-03 21:58:38 +02:00
parent b2129068a5
commit ac92e25a33
10 changed files with 336 additions and 42 deletions

View File

@ -1114,6 +1114,8 @@ in any order. E.g., these are all possible:
expr9[expr1].name expr9[expr1].name
expr9.name[expr1] expr9.name[expr1]
expr9(expr1, ...)[expr1].name expr9(expr1, ...)[expr1].name
expr9->(expr1, ...)[expr1]
Evaluation is always from left to right.
expr8[expr1] item of String or |List| *expr-[]* *E111* expr8[expr1] item of String or |List| *expr-[]* *E111*
@ -1213,6 +1215,11 @@ expr8(expr1, ...) |Funcref| function call
When expr8 is a |Funcref| type variable, invoke the function it refers to. When expr8 is a |Funcref| type variable, invoke the function it refers to.
expr8->name([args]) method call *method*
For global methods this is the same as: >
name(expr8 [, args])
There can also be methods specifically for the type of "expr8".
*expr9* *expr9*
number number
@ -2877,6 +2884,8 @@ add({object}, {expr}) *add()*
item. Use |extend()| to concatenate |Lists|. item. Use |extend()| to concatenate |Lists|.
When {object} is a |Blob| then {expr} must be a number. When {object} is a |Blob| then {expr} must be a number.
Use |insert()| to add an item at another position. Use |insert()| to add an item at another position.
Can also be used as a |method|: >
mylist->add(val1)->add(val2)
and({expr}, {expr}) *and()* and({expr}, {expr}) *and()*
@ -3512,6 +3521,8 @@ copy({expr}) Make a copy of {expr}. For Numbers and Strings this isn't
changing an item changes the contents of both |Lists|. changing an item changes the contents of both |Lists|.
A |Dictionary| is copied in a similar way as a |List|. A |Dictionary| is copied in a similar way as a |List|.
Also see |deepcopy()|. Also see |deepcopy()|.
Can also be used as a |method|: >
mylist->copy()
cos({expr}) *cos()* cos({expr}) *cos()*
Return the cosine of {expr}, measured in radians, as a |Float|. Return the cosine of {expr}, measured in radians, as a |Float|.
@ -3548,6 +3559,8 @@ count({comp}, {expr} [, {ic} [, {start}]]) *count()*
When {comp} is a string then the number of not overlapping When {comp} is a string then the number of not overlapping
occurrences of {expr} is returned. Zero is returned when occurrences of {expr} is returned. Zero is returned when
{expr} is an empty string. {expr} is an empty string.
Can also be used as a |method|: >
mylist->count(val)
*cscope_connection()* *cscope_connection()*
cscope_connection([{num} , {dbpath} [, {prepend}]]) cscope_connection([{num} , {dbpath} [, {prepend}]])
@ -3731,6 +3744,8 @@ empty({expr}) *empty()*
For a long |List| this is much faster than comparing the For a long |List| this is much faster than comparing the
length with zero. length with zero.
Can also be used as a |method|: >
mylist->empty()
escape({string}, {chars}) *escape()* escape({string}, {chars}) *escape()*
Escape the characters in {chars} that occur in {string} with a Escape the characters in {chars} that occur in {string} with a
@ -4041,6 +4056,9 @@ extend({expr1}, {expr2} [, {expr3}]) *extend()*
fails. fails.
Returns {expr1}. Returns {expr1}.
Can also be used as a |method|: >
mylist->extend(otherlist)
feedkeys({string} [, {mode}]) *feedkeys()* feedkeys({string} [, {mode}]) *feedkeys()*
Characters in {string} are queued for processing as if they Characters in {string} are queued for processing as if they
@ -4154,6 +4172,8 @@ filter({expr1}, {expr2}) *filter()*
Funcref errors inside a function are ignored, unless it was Funcref errors inside a function are ignored, unless it was
defined with the "abort" flag. defined with the "abort" flag.
Can also be used as a |method|: >
mylist->filter(expr2)
finddir({name} [, {path} [, {count}]]) *finddir()* finddir({name} [, {path} [, {count}]]) *finddir()*
Find directory {name} in {path}. Supports both downwards and Find directory {name} in {path}. Supports both downwards and
@ -4416,6 +4436,8 @@ get({list}, {idx} [, {default}]) *get()*
Get item {idx} from |List| {list}. When this item is not Get item {idx} from |List| {list}. When this item is not
available return {default}. Return zero when {default} is available return {default}. Return zero when {default} is
omitted. omitted.
Can also be used as a |method|: >
mylist->get(idx)
get({blob}, {idx} [, {default}]) get({blob}, {idx} [, {default}])
Get byte {idx} from |Blob| {blob}. When this byte is not Get byte {idx} from |Blob| {blob}. When this byte is not
available return {default}. Return -1 when {default} is available return {default}. Return -1 when {default} is
@ -5689,6 +5711,9 @@ insert({object}, {item} [, {idx}]) *insert()*
Note that when {item} is a |List| it is inserted as a single Note that when {item} is a |List| it is inserted as a single
item. Use |extend()| to concatenate |Lists|. item. Use |extend()| to concatenate |Lists|.
Can also be used as a |method|: >
mylist->insert(item)
invert({expr}) *invert()* invert({expr}) *invert()*
Bitwise invert. The argument is converted to a number. A Bitwise invert. The argument is converted to a number. A
List, Dict or Float argument causes an error. Example: > List, Dict or Float argument causes an error. Example: >
@ -5740,6 +5765,8 @@ items({dict}) *items()*
echo key . ': ' . value echo key . ': ' . value
endfor endfor
< Can also be used as a |method|: >
mydict->items()
job_ functions are documented here: |job-functions-details| job_ functions are documented here: |job-functions-details|
@ -5755,6 +5782,9 @@ join({list} [, {sep}]) *join()*
converted into a string like with |string()|. converted into a string like with |string()|.
The opposite function is |split()|. The opposite function is |split()|.
Can also be used as a |method|: >
mylist->join()
js_decode({string}) *js_decode()* js_decode({string}) *js_decode()*
This is similar to |json_decode()| with these differences: This is similar to |json_decode()| with these differences:
- Object key names do not have to be in quotes. - Object key names do not have to be in quotes.
@ -5840,7 +5870,10 @@ keys({dict}) *keys()*
Return a |List| with all the keys of {dict}. The |List| is in Return a |List| with all the keys of {dict}. The |List| is in
arbitrary order. Also see |items()| and |values()|. arbitrary order. Also see |items()| and |values()|.
*len()* *E701* Can also be used as a |method|: >
mydict->keys()
< *len()* *E701*
len({expr}) The result is a Number, which is the length of the argument. len({expr}) The result is a Number, which is the length of the argument.
When {expr} is a String or a Number the length in bytes is When {expr} is a String or a Number the length in bytes is
used, as with |strlen()|. used, as with |strlen()|.
@ -5851,7 +5884,10 @@ len({expr}) The result is a Number, which is the length of the argument.
|Dictionary| is returned. |Dictionary| is returned.
Otherwise an error is given. Otherwise an error is given.
*libcall()* *E364* *E368* Can also be used as a |method|: >
mylist->len()
< *libcall()* *E364* *E368*
libcall({libname}, {funcname}, {argument}) libcall({libname}, {funcname}, {argument})
Call function {funcname} in the run-time library {libname} Call function {funcname} in the run-time library {libname}
with single argument {argument}. with single argument {argument}.
@ -6136,6 +6172,8 @@ map({expr1}, {expr2}) *map()*
Funcref errors inside a function are ignored, unless it was Funcref errors inside a function are ignored, unless it was
defined with the "abort" flag. defined with the "abort" flag.
Can also be used as a |method|: >
mylist->map(expr2)
maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()* maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()*
When {dict} is omitted or zero: Return the rhs of mapping When {dict} is omitted or zero: Return the rhs of mapping
@ -6462,7 +6500,10 @@ max({expr}) Return the maximum value of all items in {expr}.
items in {expr} cannot be used as a Number this results in items in {expr} cannot be used as a Number this results in
an error. An empty |List| or |Dictionary| results in zero. an error. An empty |List| or |Dictionary| results in zero.
*min()* Can also be used as a |method|: >
mylist->max()
< *min()*
min({expr}) Return the minimum value of all items in {expr}. min({expr}) Return the minimum value of all items in {expr}.
{expr} can be a list or a dictionary. For a dictionary, {expr} can be a list or a dictionary. For a dictionary,
it returns the minimum of all values in the dictionary. it returns the minimum of all values in the dictionary.
@ -6470,7 +6511,10 @@ min({expr}) Return the minimum value of all items in {expr}.
items in {expr} cannot be used as a Number this results in items in {expr} cannot be used as a Number this results in
an error. An empty |List| or |Dictionary| results in zero. an error. An empty |List| or |Dictionary| results in zero.
*mkdir()* *E739* Can also be used as a |method|: >
mylist->min()
< *mkdir()* *E739*
mkdir({name} [, {path} [, {prot}]]) mkdir({name} [, {path} [, {prot}]])
Create directory {name}. Create directory {name}.
@ -7154,6 +7198,9 @@ remove({list}, {idx} [, {end}]) *remove()*
< <
Use |delete()| to remove a file. Use |delete()| to remove a file.
Can also be used as a |method|: >
mylist->remove(idx)
remove({blob}, {idx} [, {end}]) remove({blob}, {idx} [, {end}])
Without {end}: Remove the byte at {idx} from |Blob| {blob} and Without {end}: Remove the byte at {idx} from |Blob| {blob} and
return the byte. return the byte.
@ -7189,6 +7236,8 @@ repeat({expr}, {count}) *repeat()*
:let longlist = repeat(['a', 'b'], 3) :let longlist = repeat(['a', 'b'], 3)
< Results in ['a', 'b', 'a', 'b', 'a', 'b']. < Results in ['a', 'b', 'a', 'b', 'a', 'b'].
Can also be used as a |method|: >
mylist->repeat(count)
resolve({filename}) *resolve()* *E655* resolve({filename}) *resolve()* *E655*
On MS-Windows, when {filename} is a shortcut (a .lnk file), On MS-Windows, when {filename} is a shortcut (a .lnk file),
@ -7206,13 +7255,15 @@ resolve({filename}) *resolve()* *E655*
current directory (provided the result is still a relative current directory (provided the result is still a relative
path name) and also keeps a trailing path separator. path name) and also keeps a trailing path separator.
*reverse()*
reverse({object}) reverse({object}) *reverse()*
Reverse the order of items in {object} in-place. Reverse the order of items in {object} in-place.
{object} can be a |List| or a |Blob|. {object} can be a |List| or a |Blob|.
Returns {object}. Returns {object}.
If you want an object to remain unmodified make a copy first: > If you want an object to remain unmodified make a copy first: >
:let revlist = reverse(copy(mylist)) :let revlist = reverse(copy(mylist))
< Can also be used as a |method|: >
mylist->reverse()
round({expr}) *round()* round({expr}) *round()*
Round off {expr} to the nearest integral value and return it Round off {expr} to the nearest integral value and return it
@ -8070,7 +8121,10 @@ sort({list} [, {func} [, {dict}]]) *sort()* *E702*
on numbers, text strings will sort next to each other, in the on numbers, text strings will sort next to each other, in the
same order as they were originally. same order as they were originally.
Also see |uniq()|. Can also be used as a |method|: >
mylist->sort()
< Also see |uniq()|.
Example: > Example: >
func MyCompare(i1, i2) func MyCompare(i1, i2)
@ -8378,7 +8432,10 @@ string({expr}) Return {expr} converted to a String. If {expr} is a Number,
replaced by "[...]" or "{...}". Using eval() on the result replaced by "[...]" or "{...}". Using eval() on the result
will then fail. will then fail.
Also see |strtrans()|. Can also be used as a |method|: >
mylist->string()
< Also see |strtrans()|.
*strlen()* *strlen()*
strlen({expr}) The result is a Number, which is the length of the String strlen({expr}) The result is a Number, which is the length of the String
@ -9000,6 +9057,9 @@ type({expr}) The result is a Number representing the type of {expr}.
< To check if the v:t_ variables exist use this: > < To check if the v:t_ variables exist use this: >
:if exists('v:t_number') :if exists('v:t_number')
< Can also be used as a |method|: >
mylist->type()
undofile({name}) *undofile()* undofile({name}) *undofile()*
Return the name of the undo file that would be used for a file Return the name of the undo file that would be used for a file
with name {name} when writing. This uses the 'undodir' with name {name} when writing. This uses the 'undodir'
@ -9064,10 +9124,15 @@ uniq({list} [, {func} [, {dict}]]) *uniq()* *E882*
< The default compare function uses the string representation of < The default compare function uses the string representation of
each item. For the use of {func} and {dict} see |sort()|. each item. For the use of {func} and {dict} see |sort()|.
Can also be used as a |method|: >
mylist->uniq()
values({dict}) *values()* values({dict}) *values()*
Return a |List| with all the values of {dict}. The |List| is Return a |List| with all the values of {dict}. The |List| is
in arbitrary order. Also see |items()| and |keys()|. in arbitrary order. Also see |items()| and |keys()|.
Can also be used as a |method|: >
mydict->values()
virtcol({expr}) *virtcol()* virtcol({expr}) *virtcol()*
The result is a Number, which is the screen column of the file The result is a Number, which is the screen column of the file

View File

@ -4412,6 +4412,7 @@ eval6(
* + in front unary plus (ignored) * + in front unary plus (ignored)
* trailing [] subscript in String or List * trailing [] subscript in String or List
* trailing .name entry in Dictionary * trailing .name entry in Dictionary
* trailing ->name() method call
* *
* "arg" must point to the first non-white of the expression. * "arg" must point to the first non-white of the expression.
* "arg" is advanced to the next non-white after the recognized expression. * "arg" is advanced to the next non-white after the recognized expression.
@ -4690,13 +4691,12 @@ eval7(
funcexe_T funcexe; funcexe_T funcexe;
// Invoke the function. // Invoke the function.
funcexe.argv_func = NULL; vim_memset(&funcexe, 0, sizeof(funcexe));
funcexe.firstline = curwin->w_cursor.lnum; funcexe.firstline = curwin->w_cursor.lnum;
funcexe.lastline = curwin->w_cursor.lnum; funcexe.lastline = curwin->w_cursor.lnum;
funcexe.doesrange = &len; funcexe.doesrange = &len;
funcexe.evaluate = evaluate; funcexe.evaluate = evaluate;
funcexe.partial = partial; funcexe.partial = partial;
funcexe.selfdict = NULL;
ret = get_func_tv(s, len, rettv, arg, &funcexe); ret = get_func_tv(s, len, rettv, arg, &funcexe);
} }
vim_free(s); vim_free(s);
@ -4801,6 +4801,70 @@ eval7(
return ret; return ret;
} }
/*
* 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". * Evaluate an "[expr]" or "[expr:expr]" index. Also "dict.key".
* "*arg" points to the '[' or '.'. * "*arg" points to the '[' or '.'.
@ -7359,9 +7423,13 @@ check_vars(char_u *name, int len)
} }
/* /*
* Handle expr[expr], expr[expr:expr] subscript and .name lookup. * Handle:
* Also handle function call with Funcref variable: func(expr) * - expr[expr], expr[expr:expr] subscript
* Can all be combined: dict.func(expr)[idx]['func'](expr) * - ".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 int
handle_subscript( handle_subscript(
@ -7378,14 +7446,15 @@ handle_subscript(
// "." is ".name" lookup when we found a dict or when evaluating and // "." is ".name" lookup when we found a dict or when evaluating and
// scriptversion is at least 2, where string concatenation is "..". // scriptversion is at least 2, where string concatenation is "..".
while (ret == OK while (ret == OK
&& (**arg == '[' && (((**arg == '['
|| (**arg == '.' && (rettv->v_type == VAR_DICT || (**arg == '.' && (rettv->v_type == VAR_DICT
|| (!evaluate || (!evaluate
&& (*arg)[1] != '.' && (*arg)[1] != '.'
&& current_sctx.sc_version >= 2))) && current_sctx.sc_version >= 2)))
|| (**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC || (**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC
|| rettv->v_type == VAR_PARTIAL))) || rettv->v_type == VAR_PARTIAL)))
&& !VIM_ISWHITE(*(*arg - 1))) && !VIM_ISWHITE(*(*arg - 1)))
|| (**arg == '-' && (*arg)[1] == '>')))
{ {
if (**arg == '(') if (**arg == '(')
{ {
@ -7410,10 +7479,9 @@ handle_subscript(
else else
s = (char_u *)""; s = (char_u *)"";
funcexe.argv_func = NULL; vim_memset(&funcexe, 0, sizeof(funcexe));
funcexe.firstline = curwin->w_cursor.lnum; funcexe.firstline = curwin->w_cursor.lnum;
funcexe.lastline = curwin->w_cursor.lnum; funcexe.lastline = curwin->w_cursor.lnum;
funcexe.doesrange = NULL;
funcexe.evaluate = evaluate; funcexe.evaluate = evaluate;
funcexe.partial = pt; funcexe.partial = pt;
funcexe.selfdict = selfdict; funcexe.selfdict = selfdict;
@ -7436,6 +7504,14 @@ handle_subscript(
dict_unref(selfdict); dict_unref(selfdict);
selfdict = NULL; selfdict = NULL;
} }
else if (**arg == '-')
{
if (eval_method(arg, rettv, evaluate, verbose) == FAIL)
{
clear_tv(rettv);
ret = FAIL;
}
}
else /* **arg == '[' || **arg == '.' */ else /* **arg == '[' || **arg == '.' */
{ {
dict_unref(selfdict); dict_unref(selfdict);

View File

@ -412,14 +412,16 @@ static void f_xor(typval_T *argvars, typval_T *rettv);
* Array with names and number of arguments of all internal functions * Array with names and number of arguments of all internal functions
* MUST BE KEPT SORTED IN strcmp() ORDER FOR BINARY SEARCH! * MUST BE KEPT SORTED IN strcmp() ORDER FOR BINARY SEARCH!
*/ */
static struct fst typedef struct
{ {
char *f_name; /* function name */ char *f_name; /* function name */
char f_min_argc; /* minimal number of arguments */ char f_min_argc; /* minimal number of arguments */
char f_max_argc; /* maximal number of arguments */ char f_max_argc; /* maximal number of arguments */
void (*f_func)(typval_T *args, typval_T *rvar); void (*f_func)(typval_T *args, typval_T *rvar);
/* implementation of function */ /* implementation of function */
} functions[] = } funcentry_T;
static funcentry_T global_functions[] =
{ {
#ifdef FEAT_FLOAT #ifdef FEAT_FLOAT
{"abs", 1, 1, f_abs}, {"abs", 1, 1, f_abs},
@ -987,6 +989,37 @@ static struct fst
{"xor", 2, 2, f_xor}, {"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) #if defined(FEAT_CMDL_COMPL) || defined(PROTO)
/* /*
@ -1007,11 +1040,11 @@ get_function_name(expand_T *xp, int idx)
if (name != NULL) if (name != NULL)
return name; 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, "("); STRCAT(IObuff, "(");
if (functions[intidx].f_max_argc == 0) if (global_functions[intidx].f_max_argc == 0)
STRCAT(IObuff, ")"); STRCAT(IObuff, ")");
return IObuff; return IObuff;
} }
@ -1043,21 +1076,25 @@ get_expr_name(expand_T *xp, int idx)
#endif /* FEAT_CMDL_COMPL */ #endif /* FEAT_CMDL_COMPL */
/* /*
* Find internal function in table above. * Find internal function in table "functions".
* Return index, or -1 if not found * Return index, or -1 if not found
*/ */
int static int
find_internal_func( 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 first = 0;
int last = (int)(sizeof(functions) / sizeof(struct fst)) - 1; int last;
int cmp; int cmp;
int x; int x;
/* if (functions == global_functions)
* Find the function name in the table. Binary search. 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) while (first <= last)
{ {
x = first + ((unsigned)(last - first) >> 1); x = first + ((unsigned)(last - first) >> 1);
@ -1072,6 +1109,12 @@ find_internal_func(
return -1; return -1;
} }
int
has_internal_func(char_u *name)
{
return find_internal_func(name, global_functions) >= 0;
}
int int
call_internal_func( call_internal_func(
char_u *name, char_u *name,
@ -1081,15 +1124,47 @@ call_internal_func(
{ {
int i; int i;
i = find_internal_func(name); i = find_internal_func(name, global_functions);
if (i < 0) if (i < 0)
return ERROR_UNKNOWN; return ERROR_UNKNOWN;
if (argcount < functions[i].f_min_argc) if (argcount < global_functions[i].f_min_argc)
return ERROR_TOOFEW; return ERROR_TOOFEW;
if (argcount > functions[i].f_max_argc) if (argcount > global_functions[i].f_max_argc)
return ERROR_TOOMANY; return ERROR_TOOMANY;
argvars[argcount].v_type = VAR_UNKNOWN; 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; return ERROR_NONE;
} }

View File

@ -1592,6 +1592,7 @@ EXTERN char e_write[] INIT(= N_("E80: Error while writing"));
EXTERN char e_zerocount[] INIT(= N_("E939: Positive count required")); EXTERN char e_zerocount[] INIT(= N_("E939: Positive count required"));
#ifdef FEAT_EVAL #ifdef FEAT_EVAL
EXTERN char e_usingsid[] INIT(= N_("E81: Using <SID> not in a script context")); 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 #endif
#ifdef FEAT_CLIENTSERVER #ifdef FEAT_CLIENTSERVER
EXTERN char e_invexprmsg[] INIT(= N_("E449: Invalid expression received")); EXTERN char e_invexprmsg[] INIT(= N_("E449: Invalid expression received"));

View File

@ -1,8 +1,9 @@
/* evalfunc.c */ /* evalfunc.c */
char_u *get_function_name(expand_T *xp, int idx); char_u *get_function_name(expand_T *xp, int idx);
char_u *get_expr_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_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); linenr_T tv_get_lnum(typval_T *argvars);
buf_T *buflist_find_by_name(char_u *name, int curtab_only); buf_T *buflist_find_by_name(char_u *name, int curtab_only);
buf_T *tv_get_buf(typval_T *tv, int curtab_only); buf_T *tv_get_buf(typval_T *tv, int curtab_only);

View File

@ -1619,6 +1619,7 @@ typedef struct {
int evaluate; // actually evaluate expressions int evaluate; // actually evaluate expressions
partial_T *partial; // for extra arguments partial_T *partial; // for extra arguments
dict_T *selfdict; // Dictionary for "self" dict_T *selfdict; // Dictionary for "self"
typval_T *basetv; // base for base->method()
} funcexe_T; } funcexe_T;
struct partial_S struct partial_S

View File

@ -180,6 +180,7 @@ NEW_TESTS = \
test_matchadd_conceal \ test_matchadd_conceal \
test_matchadd_conceal_utf8 \ test_matchadd_conceal_utf8 \
test_memory_usage \ test_memory_usage \
test_method \
test_menu \ test_menu \
test_messages \ test_messages \
test_mksession \ test_mksession \
@ -373,6 +374,7 @@ NEW_TESTS_RES = \
test_marks.res \ test_marks.res \
test_matchadd_conceal.res \ test_matchadd_conceal.res \
test_memory_usage.res \ test_memory_usage.res \
test_method.res \
test_mksession.res \ test_mksession.res \
test_nested_function.res \ test_nested_function.res \
test_netbeans.res \ test_netbeans.res \

View File

@ -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

View File

@ -1431,10 +1431,9 @@ func_call(
{ {
funcexe_T funcexe; funcexe_T funcexe;
funcexe.argv_func = NULL; vim_memset(&funcexe, 0, sizeof(funcexe));
funcexe.firstline = curwin->w_cursor.lnum; funcexe.firstline = curwin->w_cursor.lnum;
funcexe.lastline = curwin->w_cursor.lnum; funcexe.lastline = curwin->w_cursor.lnum;
funcexe.doesrange = NULL;
funcexe.evaluate = TRUE; funcexe.evaluate = TRUE;
funcexe.partial = partial; funcexe.partial = partial;
funcexe.selfdict = selfdict; funcexe.selfdict = selfdict;
@ -1555,7 +1554,10 @@ call_func(
/* /*
* User defined function. * 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; fp = partial->pt_func;
else else
fp = find_func(rfname); fp = find_func(rfname);
@ -1625,6 +1627,14 @@ call_func(
} }
} }
} }
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 else
{ {
/* /*
@ -2715,7 +2725,7 @@ eval_fname_script(char_u *p)
translated_function_exists(char_u *name) translated_function_exists(char_u *name)
{ {
if (builtin_function(name, -1)) if (builtin_function(name, -1))
return find_internal_func(name) >= 0; return has_internal_func(name);
return find_func(name) != NULL; return find_func(name) != NULL;
} }
@ -3084,7 +3094,7 @@ ex_call(exarg_T *eap)
if (*startarg != '(') if (*startarg != '(')
{ {
semsg(_("E107: Missing parentheses: %s"), eap->arg); semsg(_(e_missingparen), eap->arg);
goto end; goto end;
} }
@ -3120,7 +3130,7 @@ ex_call(exarg_T *eap)
} }
arg = startarg; arg = startarg;
funcexe.argv_func = NULL; vim_memset(&funcexe, 0, sizeof(funcexe));
funcexe.firstline = eap->line1; funcexe.firstline = eap->line1;
funcexe.lastline = eap->line2; funcexe.lastline = eap->line2;
funcexe.doesrange = &doesrange; funcexe.doesrange = &doesrange;

View File

@ -773,6 +773,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1803,
/**/ /**/
1802, 1802,
/**/ /**/