aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/eval.c82
-rw-r--r--src/nvim/eval.lua51
-rw-r--r--src/nvim/eval/funcs.c44
-rw-r--r--src/nvim/eval/funcs.h1
-rw-r--r--src/nvim/eval/userfunc.c24
-rw-r--r--src/nvim/eval/userfunc.h2
-rw-r--r--src/nvim/generators/gen_eval.lua7
-rw-r--r--src/nvim/globals.h1
-rw-r--r--src/nvim/testdir/test_method.vim68
9 files changed, 231 insertions, 49 deletions
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