diff options
author | Sean Dewar <seandewar@users.noreply.github.com> | 2021-08-05 19:46:42 +0100 |
---|---|---|
committer | Sean Dewar <seandewar@users.noreply.github.com> | 2021-08-12 22:35:19 +0100 |
commit | e6be6c307a832d661d2a6269ad2d322e4bf5e9cc (patch) | |
tree | b2b4e54254af2af04763d4f650c43e481b36cd8b | |
parent | 4042ae5a2bc4bbca608ebb196a3d54a78d6c100c (diff) | |
download | rneovim-e6be6c307a832d661d2a6269ad2d322e4bf5e9cc.tar.gz rneovim-e6be6c307a832d661d2a6269ad2d322e4bf5e9cc.tar.bz2 rneovim-e6be6c307a832d661d2a6269ad2d322e4bf5e9cc.zip |
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.
https://github.com/vim/vim/commit/ac92e25a33c37ec5becbfffeccda136c73b761ac
- 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.
-rw-r--r-- | runtime/doc/eval.txt | 72 | ||||
-rw-r--r-- | src/nvim/eval.c | 82 | ||||
-rw-r--r-- | src/nvim/eval.lua | 51 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 44 | ||||
-rw-r--r-- | src/nvim/eval/funcs.h | 1 | ||||
-rw-r--r-- | src/nvim/eval/userfunc.c | 24 | ||||
-rw-r--r-- | src/nvim/eval/userfunc.h | 2 | ||||
-rw-r--r-- | src/nvim/generators/gen_eval.lua | 7 | ||||
-rw-r--r-- | src/nvim/globals.h | 1 | ||||
-rw-r--r-- | src/nvim/testdir/test_method.vim | 68 |
10 files changed, 299 insertions, 53 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index a76298c86c..eee401baa1 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -942,6 +942,8 @@ in any order. E.g., these are all possible: expr9[expr1].name expr9.name[expr1] expr9(expr1, ...)[expr1].name + expr9->(expr1, ...)[expr1] +Evaluation is always from left to right. 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. +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* number @@ -2603,6 +2610,8 @@ add({list}, {expr}) *add()* < Note that when {expr} is a |List| it is appended as a single item. Use |extend()| to concatenate |Lists|. 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()* @@ -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|. A |Dictionary| is copied in a similar way as a |List|. Also see |deepcopy()|. + Can also be used as a |method|: > + mylist->copy() cos({expr}) *cos()* 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 occurrences of {expr} is returned. Zero is returned when {expr} is an empty string. + Can also be used as a |method|: > + mylist->count(val) *cscope_connection()* cscope_connection([{num} , {dbpath} [, {prepend}]]) @@ -3479,6 +3492,8 @@ empty({expr}) *empty()* A |List| or |Dictionary| is empty when it does not have any items. A Number is empty when its value is zero. Special variable is empty when it is |v:false| or |v:null|. + Can also be used as a |method|: > + mylist->empty() environ() *environ()* Return all of environment variables as dictionary. You can @@ -3794,6 +3809,8 @@ extend({expr1}, {expr2} [, {expr3}]) *extend()* fails. Returns {expr1}. + Can also be used as a |method|: > + mylist->extend(otherlist) feedkeys({string} [, {mode}]) *feedkeys()* 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 defined with the "abort" flag. + Can also be used as a |method|: > + mylist->filter(expr2) finddir({name} [, {path} [, {count}]]) *finddir()* 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 available return {default}. Return zero when {default} is omitted. + Can also be used as a |method|: > + mylist->get(idx) get({dict}, {key} [, {default}]) Get item with key {key} from |Dictionary| {dict}. When this 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 item. Use |extend()| to concatenate |Lists|. + Can also be used as a |method|: > + mylist->insert(item) + interrupt() *interrupt()* Interrupt script execution. It works more or less like the 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} entry and the value of this entry. The |List| is in arbitrary order. + Can also be used as a |method|: > + mydict->items() isnan({expr}) *isnan()* 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()|. The opposite function is |split()|. + Can also be used as a |method|: > + mylist->join() + json_decode({expr}) *json_decode()* Convert {expr} from JSON object. Accepts |readfile()|-style list as the input, as well as regular string. May output any @@ -5743,8 +5772,10 @@ json_encode({expr}) *json_encode()* keys({dict}) *keys()* Return a |List| with all the keys of {dict}. The |List| is in 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. When {expr} is a String or a Number the length in bytes is 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. Otherwise an error is given. - *libcall()* *E364* *E368* + Can also be used as a |method|: > + mylist->len() + +< *libcall()* *E364* *E368* libcall({libname}, {funcname}, {argument}) Call function {funcname} in the run-time library {libname} with single argument {argument}. @@ -5938,6 +5972,8 @@ map({expr1}, {expr2}) *map()* Funcref errors inside a function are ignored, unless it was defined with the "abort" flag. + Can also be used as a |method|: > + mylist->map(expr2) maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()* 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 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()* Returns a |List| of |Dictionaries| describing |menus| (defined 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 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}]]) Create directory {name}. If {path} is "p" then intermediate directories are created as @@ -7085,6 +7127,10 @@ remove({list}, {idx} [, {end}]) *remove()* Example: > :echo "last item: " . remove(mylist, -1) :call remove(mylist, 0, 9) + +< Can also be used as a |method|: > + mylist->remove(idx) + remove({dict}, {key}) Remove the entry from {dict} with key {key} and return it. Example: > @@ -7111,6 +7157,8 @@ repeat({expr}, {count}) *repeat()* :let longlist = repeat(['a', 'b'], 3) < Results in ['a', 'b', 'a', 'b', 'a', 'b']. + Can also be used as a |method|: > + mylist->repeat(count) resolve({filename}) *resolve()* *E655* 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}. If you want a list to remain unmodified make a copy first: > :let revlist = reverse(copy(mylist)) +< Can also be used as a |method|: > + mylist->reverse() round({expr}) *round()* 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 same order as they were originally. - Also see |uniq()|. + Can also be used as a |method|: > + mylist->sort() + +< Also see |uniq()|. Example: > 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 share data with other application. + Can also be used as a |method|: > + mylist->string() + *strlen()* strlen({expr}) The result is a Number, which is the length of the String {expr} in bytes. @@ -9149,6 +9205,9 @@ type({expr}) *type()* < To check if the v:t_ variables exist use this: > :if exists('v:t_number') +< Can also be used as a |method|: > + mylist->type() + undofile({name}) *undofile()* Return the name of the undo file that would be used for a file 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 each item. For the use of {func} and {dict} see |sort()|. + Can also be used as a |method|: > + mylist->uniq() + values({dict}) *values()* Return a |List| with all the values of {dict}. The |List| is in arbitrary order. + Can also be used as a |method|: > + mydict->values() virtcol({expr}) *virtcol()* The result is a Number, which is the screen column of the file diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5a6dbf7ff4..39e8686580 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -3809,6 +3809,7 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string) // + 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. @@ -4080,6 +4081,63 @@ static int eval7( 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 /* @@ -8420,9 +8478,13 @@ int check_luafunc_name(const char *str, bool paren) } } -/// 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( const char **const arg, @@ -8456,12 +8518,11 @@ handle_subscript( } } - while (ret == OK - && (**arg == '[' - || (**arg == '.' && rettv->v_type == VAR_DICT) - || (**arg == '(' && (!evaluate || tv_is_func(*rettv)))) - && !ascii_iswhite(*(*arg - 1))) { + && (((**arg == '[' || (**arg == '.' && rettv->v_type == VAR_DICT) + || (**arg == '(' && (!evaluate || tv_is_func(*rettv)))) + && !ascii_iswhite(*(*arg - 1))) + || (**arg == '-' && (*arg)[1] == '>'))) { if (**arg == '(') { partial_T *pt = NULL; // need to copy the funcref so that we can clear rettv @@ -8507,6 +8568,11 @@ handle_subscript( } tv_dict_unref(selfdict); selfdict = NULL; + } else if (**arg == '-') { + if (eval_method((char_u **)arg, rettv, evaluate, verbose) == FAIL) { + tv_clear(rettv); + ret = FAIL; + } } else { // **arg == '[' || **arg == '.' tv_dict_unref(selfdict); if (rettv->v_type == VAR_DICT) { diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index eb20cd1bc8..f94ecffed9 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -5,6 +5,9 @@ -- args Number of arguments, list with maximum and minimum number of arguments -- or list with a minimum number of arguments only. Defaults to zero -- 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 -- `f_{funcname}`. @@ -16,7 +19,7 @@ return { funcs={ abs={args=1}, acos={args=1, func="float_op_wrapper", data="&acos"}, -- WJMc - add={args=2}, + add={args=2, base=1}, ['and']={args=2}, api_info={}, append={args=2}, @@ -73,10 +76,10 @@ return { complete_check={}, complete_info={args={0, 1}}, confirm={args={1, 4}}, - copy={args=1}, + copy={args=1, base=1}, cos={args=1, func="float_op_wrapper", data="&cos"}, cosh={args=1, func="float_op_wrapper", data="&cosh"}, - count={args={2, 4}}, + count={args={2, 4}, base=1}, cscope_connection={args={0, 3}}, ctxget={args={0, 1}}, ctxpop={}, @@ -93,7 +96,7 @@ return { did_filetype={}, diff_filler={args=1}, diff_hlID={args=2}, - empty={args=1}, + empty={args=1, base=1}, environ={}, escape={args=2}, eval={args=1}, @@ -105,12 +108,12 @@ return { exp={args=1, func="float_op_wrapper", data="&exp"}, expand={args={1, 3}}, expandcmd={args=1}, - extend={args={2, 3}}, + extend={args={2, 3}, base=1}, feedkeys={args={1, 2}}, file_readable={args=1, func='f_filereadable'}, -- obsolete filereadable={args=1}, filewritable={args=1}, - filter={args=2}, + filter={args=2, base=1}, finddir={args={1, 3}}, findfile={args={1, 3}}, flatten={args={1, 2}}, @@ -128,7 +131,7 @@ return { funcref={args={1, 3}}, ['function']={args={1, 3}}, garbagecollect={args={0, 1}}, - get={args={2, 3}}, + get={args={2, 3}, base=1}, getbufinfo={args={0, 1}}, getbufline={args={2, 3}}, getbufvar={args={2, 3}}, @@ -187,14 +190,14 @@ return { hostname={}, iconv={args=3}, indent={args=1}, - index={args={2, 4}}, + index={args={2, 4}, base=1}, input={args={1, 3}}, inputdialog={args={1, 3}}, inputlist={args=1}, inputrestore={}, inputsave={}, inputsecret={args={1, 2}}, - insert={args={2, 3}}, + insert={args={2, 3}, base=1}, interrupt={args=0}, invert={args=1}, isdirectory={args=1}, @@ -202,7 +205,7 @@ return { islocked={args=1}, isnan={args=1}, id={args=1}, - items={args=1}, + items={args=1, base=1}, jobclose={args={1, 2}, func="f_chanclose"}, jobpid={args=1}, jobresize={args=3}, @@ -210,12 +213,12 @@ return { jobstart={args={1, 2}}, jobstop={args=1}, jobwait={args={1, 2}}, - join={args={1, 2}}, + join={args={1, 2}, base=1}, json_decode={args=1}, json_encode={args=1}, - keys={args=1}, + keys={args=1, base=1}, last_buffer_nr={}, -- obsolete - len={args=1}, + len={args=1, base=1}, libcall={args=3}, libcallnr={args=3}, line={args={1, 2}}, @@ -226,7 +229,7 @@ return { log={args=1, func="float_op_wrapper", data="&log"}, log10={args=1, func="float_op_wrapper", data="&log10"}, luaeval={args={1, 2}}, - map={args=2}, + map={args=2, base=1}, maparg={args={1, 4}}, mapcheck={args={1, 3}}, match={args={2, 4}}, @@ -238,9 +241,9 @@ return { matchlist={args={2, 4}}, matchstr={args={2, 4}}, matchstrpos={args={2,4}}, - max={args=1}, + max={args=1, base=1}, menu_get={args={1, 2}}, - min={args=1}, + min={args=1, base=1}, mkdir={args={1, 3}}, mode={args={0, 1}}, msgpackdump={args=1}, @@ -270,11 +273,11 @@ return { reltime={args={0, 2}}, reltimefloat={args=1}, reltimestr={args=1}, - remove={args={2, 3}}, + remove={args={2, 3}, base=1}, rename={args=2}, - ['repeat']={args=2}, + ['repeat']={args=2, base=1}, resolve={args=1}, - reverse={args=1}, + reverse={args=1, base=1}, round={args=1, func="float_op_wrapper", data="&round"}, rpcnotify={args=varargs(2)}, rpcrequest={args=varargs(2)}, @@ -327,7 +330,7 @@ return { sin={args=1, func="float_op_wrapper", data="&sin"}, sinh={args=1, func="float_op_wrapper", data="&sinh"}, sockconnect={args={2,3}}, - sort={args={1, 3}}, + sort={args={1, 3}, base=1}, soundfold={args=1}, stdioopen={args=1}, spellbadword={args={0, 1}}, @@ -344,7 +347,7 @@ return { strftime={args={1, 2}}, strgetchar={args={2, 2}}, stridx={args={2, 3}}, - string={args=1}, + string={args=1, base=1}, strlen={args=1}, strpart={args={2, 4}}, strptime={args=2}, @@ -383,11 +386,11 @@ return { tr={args=3}, trim={args={1,3}}, trunc={args=1, func="float_op_wrapper", data="&trunc"}, - type={args=1}, + type={args=1, base=1}, undofile={args=1}, undotree={}, - uniq={args={1, 3}}, - values={args=1}, + uniq={args={1, 3}, base=1}, + values={args=1, base=1}, virtcol={args=1}, visualmode={args={0, 1}}, wait={args={2,3}}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 363640adc5..787d5aaf78 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -175,6 +175,50 @@ 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 || 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. */ diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h index a343290734..3ad0b8282a 100644 --- a/src/nvim/eval/funcs.h +++ b/src/nvim/eval/funcs.h @@ -14,6 +14,7 @@ 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), or 0 if not a method. 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 516f7a1d53..ec43987b07 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -1514,7 +1514,10 @@ call_func( } } else if (fp != NULL || !builtin_function((const char *)rfname, -1)) { // 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); } @@ -1560,20 +1563,13 @@ call_func( 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 { // 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 @@ -2937,7 +2933,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; } diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h index 9af35e0411..513473449a 100644 --- a/src/nvim/eval/userfunc.h +++ b/src/nvim/eval/userfunc.h @@ -44,6 +44,7 @@ typedef struct { 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) { \ @@ -54,6 +55,7 @@ typedef struct { .evaluate = false, \ .partial = NULL, \ .selfdict = NULL, \ + .basetv = NULL, \ } #define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua index 679895421a..aa2274e0a2 100644 --- a/src/nvim/generators/gen_eval.lua +++ b/src/nvim/generators/gen_eval.lua @@ -42,7 +42,7 @@ gperfpipe:write([[ %language=ANSI-C %global-table %readonly-tables -%define initializer-suffix ,0,0,NULL,NULL +%define initializer-suffix ,0,0,0,NULL,NULL %define word-array-name functions %define hash-function-name hash_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 args[2] = 'MAX_FUNC_ARGS' end + local base = def.base or 0 local func = def.func or ('f_' .. name) local data = def.data or "NULL" - gperfpipe:write(('%s, %s, %s, &%s, (FunPtr)%s\n') - :format(name, args[1], args[2], func, data)) + gperfpipe:write(('%s, %s, %s, %s, &%s, (FunPtr)%s\n') + :format(name, args[1], args[2], base, func, data)) end gperfpipe:close() diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 96acca4ac7..2a72dbcd09 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -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_usingsid[] INIT(= N_( "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_( "E363: pattern uses more memory than 'maxmempattern'")); EXTERN char_u e_emptybuf[] INIT(= N_("E749: empty buffer")); diff --git a/src/nvim/testdir/test_method.vim b/src/nvim/testdir/test_method.vim new file mode 100644 index 0000000000..1c0de01ac5 --- /dev/null +++ b/src/nvim/testdir/test_method.vim @@ -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 |