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.
ac92e25a33

- Note that to *exactly* port hunk @@ -7376,18 +7444,19 from
  handle_subscript(), we need the :scriptversion patches (I have an open
  PR for those, but this patch works fine without them anyway).
- Port call_internal_func() from v7.4.2058.
- Adjust some error messages in tests, as they rely on the Blob patches.
- Add a modeline to test_method.vim.

Ignore the global_functions and base_method tables and prefer the
current GPerf implementation. Instead, add an extra base_arg field to
VimLFuncDef that holds the number of the argument to use as the base
(1-indexed, so that 0 may be used to refer to functions that cannot be
used as methods).

This also means we support using any argument as a base from the get-go,
rather than just the first (Vim includes this ability in future patches,
however).

To mark a function as usable as a method, use the "base" key as
described in eval.lua.
This commit is contained in:
Sean Dewar 2021-08-05 19:46:42 +01:00
parent 4042ae5a2b
commit e6be6c307a
No known key found for this signature in database
GPG Key ID: 08CC2C83AD41B581
10 changed files with 299 additions and 53 deletions

View File

@ -942,6 +942,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*
@ -1043,6 +1045,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
@ -2603,6 +2610,8 @@ add({list}, {expr}) *add()*
< Note that when {expr} is a |List| it is appended as a single < Note that when {expr} is a |List| it is appended as a single
item. Use |extend()| to concatenate |Lists|. item. Use |extend()| to concatenate |Lists|.
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()*
@ -3215,6 +3224,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|.
@ -3249,6 +3260,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}]])
@ -3479,6 +3492,8 @@ empty({expr}) *empty()*
A |List| or |Dictionary| is empty when it does not have any A |List| or |Dictionary| is empty when it does not have any
items. A Number is empty when its value is zero. Special items. A Number is empty when its value is zero. Special
variable is empty when it is |v:false| or |v:null|. variable is empty when it is |v:false| or |v:null|.
Can also be used as a |method|: >
mylist->empty()
environ() *environ()* environ() *environ()*
Return all of environment variables as dictionary. You can Return all of environment variables as dictionary. You can
@ -3794,6 +3809,8 @@ 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
@ -3903,6 +3920,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
@ -4159,6 +4178,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({dict}, {key} [, {default}]) get({dict}, {key} [, {default}])
Get item with key {key} from |Dictionary| {dict}. When this Get item with key {key} from |Dictionary| {dict}. When this
item is not available return {default}. Return zero when item is not available return {default}. Return zero when
@ -5513,6 +5534,9 @@ insert({list}, {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)
interrupt() *interrupt()* interrupt() *interrupt()*
Interrupt script execution. It works more or less like the Interrupt script execution. It works more or less like the
user typing CTRL-C, most commands won't execute and control user typing CTRL-C, most commands won't execute and control
@ -5580,6 +5604,8 @@ items({dict}) *items()*
|List| item is a list with two items: the key of a {dict} |List| item is a list with two items: the key of a {dict}
entry and the value of this entry. The |List| is in arbitrary entry and the value of this entry. The |List| is in arbitrary
order. order.
Can also be used as a |method|: >
mydict->items()
isnan({expr}) *isnan()* isnan({expr}) *isnan()*
Return |TRUE| if {expr} is a float with value NaN. > Return |TRUE| if {expr} is a float with value NaN. >
@ -5713,6 +5739,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()
json_decode({expr}) *json_decode()* json_decode({expr}) *json_decode()*
Convert {expr} from JSON object. Accepts |readfile()|-style Convert {expr} from JSON object. Accepts |readfile()|-style
list as the input, as well as regular string. May output any list as the input, as well as regular string. May output any
@ -5743,8 +5772,10 @@ json_encode({expr}) *json_encode()*
keys({dict}) *keys()* 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. arbitrary order.
Can also be used as a |method|: >
mydict->keys()
*len()* *E701* < *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()|.
@ -5755,7 +5786,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}.
@ -5938,6 +5972,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
@ -6273,6 +6309,9 @@ 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.
Can also be used as a |method|: >
mylist->max()
menu_get({path}, {modes}) *menu_get()* menu_get({path}, {modes}) *menu_get()*
Returns a |List| of |Dictionaries| describing |menus| (defined Returns a |List| of |Dictionaries| describing |menus| (defined
by |:menu|, |:amenu|, …), including |hidden-menus|. by |:menu|, |:amenu|, …), including |hidden-menus|.
@ -6327,7 +6366,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}.
If {path} is "p" then intermediate directories are created as If {path} is "p" then intermediate directories are created as
@ -7085,6 +7127,10 @@ remove({list}, {idx} [, {end}]) *remove()*
Example: > Example: >
:echo "last item: " . remove(mylist, -1) :echo "last item: " . remove(mylist, -1)
:call remove(mylist, 0, 9) :call remove(mylist, 0, 9)
< Can also be used as a |method|: >
mylist->remove(idx)
remove({dict}, {key}) remove({dict}, {key})
Remove the entry from {dict} with key {key} and return it. Remove the entry from {dict} with key {key} and return it.
Example: > Example: >
@ -7111,6 +7157,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),
@ -7130,6 +7178,8 @@ reverse({list}) Reverse the order of items in {list} in-place. Returns
{list}. {list}.
If you want a list to remain unmodified make a copy first: > If you want a list 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
@ -8209,7 +8259,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)
@ -8504,6 +8557,9 @@ string({expr}) Return {expr} converted to a String. If {expr} is a Number,
method, use |msgpackdump()| or |json_encode()| if you need to method, use |msgpackdump()| or |json_encode()| if you need to
share data with other application. share data with other application.
Can also be used as a |method|: >
mylist->string()
*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
{expr} in bytes. {expr} in bytes.
@ -9149,6 +9205,9 @@ type({expr}) *type()*
< 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'
@ -9211,10 +9270,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. in arbitrary order.
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

@ -3809,6 +3809,7 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string)
// + 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.
@ -4080,6 +4081,63 @@ static int eval7(
return ret; return ret;
} }
/// Evaluate "->method()".
/// @note "*arg" points to the '-'.
/// @return FAIL or OK. "*arg" is advanced to after the ')'.
static int eval_method(char_u **const arg, typval_T *const rettv,
const bool evaluate, const bool verbose)
FUNC_ATTR_NONNULL_ALL
{
// Skip over the ->.
*arg += 2;
// Locate the method name.
const char_u *const name = *arg;
size_t len;
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) {
EMSG2(_(e_missingparen), name);
}
return FAIL;
}
*arg += len;
typval_T base = *rettv;
funcexe_T funcexe = FUNCEXE_INIT;
funcexe.evaluate = evaluate;
funcexe.basetv = &base;
rettv->v_type = VAR_UNKNOWN;
int 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) {
tv_clear(&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) {
tv_clear(rettv);
}
ret = FAIL;
}
return ret;
}
// TODO(ZyX-I): move to eval/expressions // TODO(ZyX-I): move to eval/expressions
/* /*
@ -8420,9 +8478,13 @@ int check_luafunc_name(const char *str, bool paren)
} }
} }
/// 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(
const char **const arg, const char **const arg,
@ -8456,12 +8518,11 @@ handle_subscript(
} }
} }
while (ret == OK while (ret == OK
&& (**arg == '[' && (((**arg == '[' || (**arg == '.' && rettv->v_type == VAR_DICT)
|| (**arg == '.' && rettv->v_type == VAR_DICT) || (**arg == '(' && (!evaluate || tv_is_func(*rettv))))
|| (**arg == '(' && (!evaluate || tv_is_func(*rettv)))) && !ascii_iswhite(*(*arg - 1)))
&& !ascii_iswhite(*(*arg - 1))) { || (**arg == '-' && (*arg)[1] == '>'))) {
if (**arg == '(') { if (**arg == '(') {
partial_T *pt = NULL; partial_T *pt = NULL;
// need to copy the funcref so that we can clear rettv // need to copy the funcref so that we can clear rettv
@ -8507,6 +8568,11 @@ handle_subscript(
} }
tv_dict_unref(selfdict); tv_dict_unref(selfdict);
selfdict = NULL; selfdict = NULL;
} else if (**arg == '-') {
if (eval_method((char_u **)arg, rettv, evaluate, verbose) == FAIL) {
tv_clear(rettv);
ret = FAIL;
}
} else { // **arg == '[' || **arg == '.' } else { // **arg == '[' || **arg == '.'
tv_dict_unref(selfdict); tv_dict_unref(selfdict);
if (rettv->v_type == VAR_DICT) { if (rettv->v_type == VAR_DICT) {

View File

@ -5,6 +5,9 @@
-- args Number of arguments, list with maximum and minimum number of arguments -- args Number of arguments, list with maximum and minimum number of arguments
-- or list with a minimum number of arguments only. Defaults to zero -- or list with a minimum number of arguments only. Defaults to zero
-- arguments. -- arguments.
-- base For methods: the argument to use as the base argument (1-indexed):
-- base->method()
-- Defaults to zero (function cannot be used as a method).
-- func Name of the C function which implements the VimL function. Defaults to -- func Name of the C function which implements the VimL function. Defaults to
-- `f_{funcname}`. -- `f_{funcname}`.
@ -16,7 +19,7 @@ return {
funcs={ funcs={
abs={args=1}, abs={args=1},
acos={args=1, func="float_op_wrapper", data="&acos"}, -- WJMc acos={args=1, func="float_op_wrapper", data="&acos"}, -- WJMc
add={args=2}, add={args=2, base=1},
['and']={args=2}, ['and']={args=2},
api_info={}, api_info={},
append={args=2}, append={args=2},
@ -73,10 +76,10 @@ return {
complete_check={}, complete_check={},
complete_info={args={0, 1}}, complete_info={args={0, 1}},
confirm={args={1, 4}}, confirm={args={1, 4}},
copy={args=1}, copy={args=1, base=1},
cos={args=1, func="float_op_wrapper", data="&cos"}, cos={args=1, func="float_op_wrapper", data="&cos"},
cosh={args=1, func="float_op_wrapper", data="&cosh"}, cosh={args=1, func="float_op_wrapper", data="&cosh"},
count={args={2, 4}}, count={args={2, 4}, base=1},
cscope_connection={args={0, 3}}, cscope_connection={args={0, 3}},
ctxget={args={0, 1}}, ctxget={args={0, 1}},
ctxpop={}, ctxpop={},
@ -93,7 +96,7 @@ return {
did_filetype={}, did_filetype={},
diff_filler={args=1}, diff_filler={args=1},
diff_hlID={args=2}, diff_hlID={args=2},
empty={args=1}, empty={args=1, base=1},
environ={}, environ={},
escape={args=2}, escape={args=2},
eval={args=1}, eval={args=1},
@ -105,12 +108,12 @@ return {
exp={args=1, func="float_op_wrapper", data="&exp"}, exp={args=1, func="float_op_wrapper", data="&exp"},
expand={args={1, 3}}, expand={args={1, 3}},
expandcmd={args=1}, expandcmd={args=1},
extend={args={2, 3}}, extend={args={2, 3}, base=1},
feedkeys={args={1, 2}}, feedkeys={args={1, 2}},
file_readable={args=1, func='f_filereadable'}, -- obsolete file_readable={args=1, func='f_filereadable'}, -- obsolete
filereadable={args=1}, filereadable={args=1},
filewritable={args=1}, filewritable={args=1},
filter={args=2}, filter={args=2, base=1},
finddir={args={1, 3}}, finddir={args={1, 3}},
findfile={args={1, 3}}, findfile={args={1, 3}},
flatten={args={1, 2}}, flatten={args={1, 2}},
@ -128,7 +131,7 @@ return {
funcref={args={1, 3}}, funcref={args={1, 3}},
['function']={args={1, 3}}, ['function']={args={1, 3}},
garbagecollect={args={0, 1}}, garbagecollect={args={0, 1}},
get={args={2, 3}}, get={args={2, 3}, base=1},
getbufinfo={args={0, 1}}, getbufinfo={args={0, 1}},
getbufline={args={2, 3}}, getbufline={args={2, 3}},
getbufvar={args={2, 3}}, getbufvar={args={2, 3}},
@ -187,14 +190,14 @@ return {
hostname={}, hostname={},
iconv={args=3}, iconv={args=3},
indent={args=1}, indent={args=1},
index={args={2, 4}}, index={args={2, 4}, base=1},
input={args={1, 3}}, input={args={1, 3}},
inputdialog={args={1, 3}}, inputdialog={args={1, 3}},
inputlist={args=1}, inputlist={args=1},
inputrestore={}, inputrestore={},
inputsave={}, inputsave={},
inputsecret={args={1, 2}}, inputsecret={args={1, 2}},
insert={args={2, 3}}, insert={args={2, 3}, base=1},
interrupt={args=0}, interrupt={args=0},
invert={args=1}, invert={args=1},
isdirectory={args=1}, isdirectory={args=1},
@ -202,7 +205,7 @@ return {
islocked={args=1}, islocked={args=1},
isnan={args=1}, isnan={args=1},
id={args=1}, id={args=1},
items={args=1}, items={args=1, base=1},
jobclose={args={1, 2}, func="f_chanclose"}, jobclose={args={1, 2}, func="f_chanclose"},
jobpid={args=1}, jobpid={args=1},
jobresize={args=3}, jobresize={args=3},
@ -210,12 +213,12 @@ return {
jobstart={args={1, 2}}, jobstart={args={1, 2}},
jobstop={args=1}, jobstop={args=1},
jobwait={args={1, 2}}, jobwait={args={1, 2}},
join={args={1, 2}}, join={args={1, 2}, base=1},
json_decode={args=1}, json_decode={args=1},
json_encode={args=1}, json_encode={args=1},
keys={args=1}, keys={args=1, base=1},
last_buffer_nr={}, -- obsolete last_buffer_nr={}, -- obsolete
len={args=1}, len={args=1, base=1},
libcall={args=3}, libcall={args=3},
libcallnr={args=3}, libcallnr={args=3},
line={args={1, 2}}, line={args={1, 2}},
@ -226,7 +229,7 @@ return {
log={args=1, func="float_op_wrapper", data="&log"}, log={args=1, func="float_op_wrapper", data="&log"},
log10={args=1, func="float_op_wrapper", data="&log10"}, log10={args=1, func="float_op_wrapper", data="&log10"},
luaeval={args={1, 2}}, luaeval={args={1, 2}},
map={args=2}, map={args=2, base=1},
maparg={args={1, 4}}, maparg={args={1, 4}},
mapcheck={args={1, 3}}, mapcheck={args={1, 3}},
match={args={2, 4}}, match={args={2, 4}},
@ -238,9 +241,9 @@ return {
matchlist={args={2, 4}}, matchlist={args={2, 4}},
matchstr={args={2, 4}}, matchstr={args={2, 4}},
matchstrpos={args={2,4}}, matchstrpos={args={2,4}},
max={args=1}, max={args=1, base=1},
menu_get={args={1, 2}}, menu_get={args={1, 2}},
min={args=1}, min={args=1, base=1},
mkdir={args={1, 3}}, mkdir={args={1, 3}},
mode={args={0, 1}}, mode={args={0, 1}},
msgpackdump={args=1}, msgpackdump={args=1},
@ -270,11 +273,11 @@ return {
reltime={args={0, 2}}, reltime={args={0, 2}},
reltimefloat={args=1}, reltimefloat={args=1},
reltimestr={args=1}, reltimestr={args=1},
remove={args={2, 3}}, remove={args={2, 3}, base=1},
rename={args=2}, rename={args=2},
['repeat']={args=2}, ['repeat']={args=2, base=1},
resolve={args=1}, resolve={args=1},
reverse={args=1}, reverse={args=1, base=1},
round={args=1, func="float_op_wrapper", data="&round"}, round={args=1, func="float_op_wrapper", data="&round"},
rpcnotify={args=varargs(2)}, rpcnotify={args=varargs(2)},
rpcrequest={args=varargs(2)}, rpcrequest={args=varargs(2)},
@ -327,7 +330,7 @@ return {
sin={args=1, func="float_op_wrapper", data="&sin"}, sin={args=1, func="float_op_wrapper", data="&sin"},
sinh={args=1, func="float_op_wrapper", data="&sinh"}, sinh={args=1, func="float_op_wrapper", data="&sinh"},
sockconnect={args={2,3}}, sockconnect={args={2,3}},
sort={args={1, 3}}, sort={args={1, 3}, base=1},
soundfold={args=1}, soundfold={args=1},
stdioopen={args=1}, stdioopen={args=1},
spellbadword={args={0, 1}}, spellbadword={args={0, 1}},
@ -344,7 +347,7 @@ return {
strftime={args={1, 2}}, strftime={args={1, 2}},
strgetchar={args={2, 2}}, strgetchar={args={2, 2}},
stridx={args={2, 3}}, stridx={args={2, 3}},
string={args=1}, string={args=1, base=1},
strlen={args=1}, strlen={args=1},
strpart={args={2, 4}}, strpart={args={2, 4}},
strptime={args=2}, strptime={args=2},
@ -383,11 +386,11 @@ return {
tr={args=3}, tr={args=3},
trim={args={1,3}}, trim={args={1,3}},
trunc={args=1, func="float_op_wrapper", data="&trunc"}, trunc={args=1, func="float_op_wrapper", data="&trunc"},
type={args=1}, type={args=1, base=1},
undofile={args=1}, undofile={args=1},
undotree={}, undotree={},
uniq={args={1, 3}}, uniq={args={1, 3}, base=1},
values={args=1}, values={args=1, base=1},
virtcol={args=1}, virtcol={args=1},
visualmode={args={0, 1}}, visualmode={args={0, 1}},
wait={args={2,3}}, wait={args={2,3}},

View File

@ -175,6 +175,50 @@ const VimLFuncDef *find_internal_func(const char *const name)
return find_internal_func_gperf(name, len); 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 || fdef->base_arg == 0) {
return ERROR_UNKNOWN;
} 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 - 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. * Return TRUE for a non-zero Number and a non-empty String.
*/ */

View File

@ -14,6 +14,7 @@ typedef struct fst {
char *name; ///< Name of the function. char *name; ///< Name of the function.
uint8_t min_argc; ///< Minimal number of arguments. uint8_t min_argc; ///< Minimal number of arguments.
uint8_t max_argc; ///< Maximal number of arguments. uint8_t max_argc; ///< Maximal number of arguments.
uint8_t base_arg; ///< Method base arg # (1-indexed), or 0 if not a method.
VimLFunc func; ///< Function implementation. VimLFunc func; ///< Function implementation.
FunPtr data; ///< Userdata for function implementation. FunPtr data; ///< Userdata for function implementation.
} VimLFuncDef; } VimLFuncDef;

View File

@ -1514,7 +1514,10 @@ call_func(
} }
} else if (fp != NULL || !builtin_function((const char *)rfname, -1)) { } else if (fp != NULL || !builtin_function((const char *)rfname, -1)) {
// User defined function. // User defined function.
if (fp == NULL) { if (funcexe->basetv != NULL) {
// TODO(seandewar): support User function: base->Method()
fp = NULL;
} else if (fp == NULL) {
fp = find_func(rfname); fp = find_func(rfname);
} }
@ -1560,20 +1563,13 @@ call_func(
error = ERROR_NONE; error = ERROR_NONE;
} }
} }
} 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 {
// Find the function name in the table, call its implementation. // Find the function name in the table, call its implementation.
const VimLFuncDef *const fdef = find_internal_func((const char *)fname); error = call_internal_func(fname, argcount, argvars, rettv);
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;
}
}
} }
/* /*
* The function call (or "FuncUndefined" autocommand sequence) might * The function call (or "FuncUndefined" autocommand sequence) might
@ -2937,7 +2933,7 @@ void ex_call(exarg_T *eap)
rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this. rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this.
if (*startarg != '(') { if (*startarg != '(') {
EMSG2(_("E107: Missing parentheses: %s"), eap->arg); EMSG2(_(e_missingparen), eap->arg);
goto end; goto end;
} }

View File

@ -44,6 +44,7 @@ typedef struct {
bool evaluate; ///< actually evaluate expressions bool 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;
#define FUNCEXE_INIT (funcexe_T) { \ #define FUNCEXE_INIT (funcexe_T) { \
@ -54,6 +55,7 @@ typedef struct {
.evaluate = false, \ .evaluate = false, \
.partial = NULL, \ .partial = NULL, \
.selfdict = NULL, \ .selfdict = NULL, \
.basetv = NULL, \
} }
#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] #define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j]

View File

@ -42,7 +42,7 @@ gperfpipe:write([[
%language=ANSI-C %language=ANSI-C
%global-table %global-table
%readonly-tables %readonly-tables
%define initializer-suffix ,0,0,NULL,NULL %define initializer-suffix ,0,0,0,NULL,NULL
%define word-array-name functions %define word-array-name functions
%define hash-function-name hash_internal_func_gperf %define hash-function-name hash_internal_func_gperf
%define lookup-function-name find_internal_func_gperf %define lookup-function-name find_internal_func_gperf
@ -59,9 +59,10 @@ for name, def in pairs(funcs) do
elseif #args == 1 then elseif #args == 1 then
args[2] = 'MAX_FUNC_ARGS' args[2] = 'MAX_FUNC_ARGS'
end end
local base = def.base or 0
local func = def.func or ('f_' .. name) local func = def.func or ('f_' .. name)
local data = def.data or "NULL" local data = def.data or "NULL"
gperfpipe:write(('%s, %s, %s, &%s, (FunPtr)%s\n') gperfpipe:write(('%s, %s, %s, %s, &%s, (FunPtr)%s\n')
:format(name, args[1], args[2], func, data)) :format(name, args[1], args[2], base, func, data))
end end
gperfpipe:close() gperfpipe:close()

View File

@ -972,6 +972,7 @@ EXTERN char_u e_write[] INIT(= N_("E80: Error while writing"));
EXTERN char_u e_zerocount[] INIT(= N_("E939: Positive count required")); EXTERN char_u e_zerocount[] INIT(= N_("E939: Positive count required"));
EXTERN char_u e_usingsid[] INIT(= N_( EXTERN char_u e_usingsid[] INIT(= N_(
"E81: Using <SID> not in a script context")); "E81: Using <SID> not in a script context"));
EXTERN char_u e_missingparen[] INIT(= N_("E107: Missing parentheses: %s"));
EXTERN char_u e_maxmempat[] INIT(= N_( EXTERN char_u e_maxmempat[] INIT(= N_(
"E363: pattern uses more memory than 'maxmempattern'")); "E363: pattern uses more memory than 'maxmempattern'"));
EXTERN char_u e_emptybuf[] INIT(= N_("E749: empty buffer")); EXTERN char_u e_emptybuf[] INIT(= N_("E749: empty buffer"));

View File

@ -0,0 +1,68 @@
" 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'))
" Nvim doesn't support Blobs yet; expect a different emsg
" call assert_fails("let x = d->index(2)", 'E897:')
" call assert_fails("let x = d->insert(0)", 'E899:')
call assert_fails("let x = d->index(2)", 'E714:')
call assert_fails("let x = d->insert(0)", 'E686:')
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:')
" Nvim doesn't support Blobs yet; expect a different emsg
" call assert_fails('let x = d->reverse()', 'E899:')
call assert_fails('let x = d->reverse()', 'E686:')
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
" vim: shiftwidth=2 sts=2 expandtab