Patch 8.1.1803

25 views
Skip to first unread message

Bram Moolenaar

unread,
Aug 3, 2019, 3:59:04 PM8/3/19
to vim...@googlegroups.com

Patch 8.1.1803
Problem: All builtin functions are global.
Solution: Add the method call operator ->. Implemented for a limited number
of functions.
Files: runtime/doc/eval.txt, src/eval.c, src/structs.h, src/userfunc.c,
src/globals.h, src/evalfunc.c, src/proto/evalfunc.pro,
src/testdir/test_method.vim, src/testdir/Make_all.mak


*** ../vim-8.1.1802/runtime/doc/eval.txt 2019-07-28 17:57:04.845046867 +0200
--- runtime/doc/eval.txt 2019-08-03 21:09:44.172857659 +0200
***************
*** 1114,1119 ****
--- 1114,1121 ----
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*
***************
*** 1213,1218 ****
--- 1215,1225 ----
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
***************
*** 2877,2882 ****
--- 2884,2891 ----
item. Use |extend()| to concatenate |Lists|.
When {object} is a |Blob| then {expr} must be a number.
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()*
***************
*** 3512,3517 ****
--- 3521,3528 ----
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|.
***************
*** 3548,3553 ****
--- 3559,3566 ----
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}]])
***************
*** 3731,3736 ****
--- 3744,3751 ----

For a long |List| this is much faster than comparing the
length with zero.
+ Can also be used as a |method|: >
+ mylist->empty()

escape({string}, {chars}) *escape()*
Escape the characters in {chars} that occur in {string} with a
***************
*** 4041,4046 ****
--- 4056,4064 ----
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
***************
*** 4154,4159 ****
--- 4177,4184 ----
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
***************
*** 4416,4421 ****
--- 4441,4448 ----
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({blob}, {idx} [, {default}])
Get byte {idx} from |Blob| {blob}. When this byte is not
available return {default}. Return -1 when {default} is
***************
*** 5685,5690 ****
--- 5716,5724 ----
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)
+
invert({expr}) *invert()*
Bitwise invert. The argument is converted to a number. A
List, Dict or Float argument causes an error. Example: >
***************
*** 5736,5741 ****
--- 5770,5777 ----
echo key . ': ' . value
endfor

+ < Can also be used as a |method|: >
+ mydict->items()

job_ functions are documented here: |job-functions-details|

***************
*** 5751,5756 ****
--- 5787,5795 ----
converted into a string like with |string()|.
The opposite function is |split()|.

+ Can also be used as a |method|: >
+ mylist->join()
+
js_decode({string}) *js_decode()*
This is similar to |json_decode()| with these differences:
- Object key names do not have to be in quotes.
***************
*** 5836,5842 ****
Return a |List| with all the keys of {dict}. The |List| is in
arbitrary order. Also see |items()| and |values()|.

! *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()|.
--- 5875,5884 ----
Return a |List| with all the keys of {dict}. The |List| is in
arbitrary order. Also see |items()| and |values()|.

! 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.
When {expr} is a String or a Number the length in bytes is
used, as with |strlen()|.
***************
*** 5847,5853 ****
|Dictionary| is returned.
Otherwise an error is given.

! *libcall()* *E364* *E368*
libcall({libname}, {funcname}, {argument})
Call function {funcname} in the run-time library {libname}
with single argument {argument}.
--- 5889,5898 ----
|Dictionary| is returned.
Otherwise an error is given.

! 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}.
***************
*** 6132,6137 ****
--- 6177,6184 ----
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
***************
*** 6458,6464 ****
items in {expr} cannot be used as a Number this results in
an error. An empty |List| or |Dictionary| results in zero.

! *min()*
min({expr}) Return the minimum value of all items in {expr}.
{expr} can be a list or a dictionary. For a dictionary,
it returns the minimum of all values in the dictionary.
--- 6505,6514 ----
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()
!
! < *min()*
min({expr}) Return the minimum value of all items in {expr}.
{expr} can be a list or a dictionary. For a dictionary,
it returns the minimum of all values in the dictionary.
***************
*** 6466,6472 ****
items in {expr} cannot be used as a Number this results in
an error. An empty |List| or |Dictionary| results in zero.

! *mkdir()* *E739*
mkdir({name} [, {path} [, {prot}]])
Create directory {name}.

--- 6516,6525 ----
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->min()
!
! < *mkdir()* *E739*
mkdir({name} [, {path} [, {prot}]])
Create directory {name}.

***************
*** 7150,7155 ****
--- 7203,7211 ----
<
Use |delete()| to remove a file.

+ Can also be used as a |method|: >
+ mylist->remove(idx)
+
remove({blob}, {idx} [, {end}])
Without {end}: Remove the byte at {idx} from |Blob| {blob} and
return the byte.
***************
*** 7184,7189 ****
--- 7241,7248 ----
: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),
***************
*** 7201,7213 ****
current directory (provided the result is still a relative
path name) and also keeps a trailing path separator.

! *reverse()*
! reverse({object})
Reverse the order of items in {object} in-place.
{object} can be a |List| or a |Blob|.
Returns {object}.
If you want an object to remain unmodified make a copy first: >
:let revlist = reverse(copy(mylist))

round({expr}) *round()*
Round off {expr} to the nearest integral value and return it
--- 7260,7274 ----
current directory (provided the result is still a relative
path name) and also keeps a trailing path separator.

!
! reverse({object}) *reverse()*
Reverse the order of items in {object} in-place.
{object} can be a |List| or a |Blob|.
Returns {object}.
If you want an object 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
***************
*** 8065,8071 ****
on numbers, text strings will sort next to each other, in the
same order as they were originally.

! Also see |uniq()|.

Example: >
func MyCompare(i1, i2)
--- 8126,8135 ----
on numbers, text strings will sort next to each other, in the
same order as they were originally.

! Can also be used as a |method|: >
! mylist->sort()
!
! < Also see |uniq()|.

Example: >
func MyCompare(i1, i2)
***************
*** 8373,8379 ****
replaced by "[...]" or "{...}". Using eval() on the result
will then fail.

! Also see |strtrans()|.

*strlen()*
strlen({expr}) The result is a Number, which is the length of the String
--- 8437,8446 ----
replaced by "[...]" or "{...}". Using eval() on the result
will then fail.

! Can also be used as a |method|: >
! mylist->string()
!
! < Also see |strtrans()|.

*strlen()*
strlen({expr}) The result is a Number, which is the length of the String
***************
*** 8995,9000 ****
--- 9062,9070 ----
< 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'
***************
*** 9059,9068 ****
--- 9129,9143 ----
< 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. Also see |items()| and |keys()|.

+ 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
*** ../vim-8.1.1802/src/eval.c 2019-08-03 18:17:07.680638632 +0200
--- src/eval.c 2019-08-03 19:03:27.140396230 +0200
***************
*** 4412,4417 ****
--- 4412,4418 ----
* + in front unary plus (ignored)
* trailing [] subscript in String or List
* trailing .name entry in Dictionary
+ * trailing ->name() method call
*
* "arg" must point to the first non-white of the expression.
* "arg" is advanced to the next non-white after the recognized expression.
***************
*** 4690,4702 ****
funcexe_T funcexe;

// Invoke the function.
! funcexe.argv_func = NULL;
funcexe.firstline = curwin->w_cursor.lnum;
funcexe.lastline = curwin->w_cursor.lnum;
funcexe.doesrange = &len;
funcexe.evaluate = evaluate;
funcexe.partial = partial;
- funcexe.selfdict = NULL;
ret = get_func_tv(s, len, rettv, arg, &funcexe);
}
vim_free(s);
--- 4691,4702 ----
funcexe_T funcexe;

// Invoke the function.
! vim_memset(&funcexe, 0, sizeof(funcexe));
funcexe.firstline = curwin->w_cursor.lnum;
funcexe.lastline = curwin->w_cursor.lnum;
funcexe.doesrange = &len;
funcexe.evaluate = evaluate;
funcexe.partial = partial;
ret = get_func_tv(s, len, rettv, arg, &funcexe);
}
vim_free(s);
***************
*** 4802,4807 ****
--- 4802,4871 ----
}

/*
+ * Evaluate "->method()".
+ * "*arg" points to the '-'.
+ * Returns FAIL or OK. "*arg" is advanced to after the ')'.
+ */
+ static int
+ eval_method(
+ char_u **arg,
+ typval_T *rettv,
+ int evaluate,
+ int verbose) /* give error messages */
+ {
+ char_u *name;
+ long len;
+ funcexe_T funcexe;
+ int ret = OK;
+ typval_T base = *rettv;
+
+ // Skip over the ->.
+ *arg += 2;
+
+ // Locate the method name.
+ name = *arg;
+ for (len = 0; ASCII_ISALNUM(name[len]) || name[len] == '_'; ++len)
+ ;
+ if (len == 0)
+ {
+ if (verbose)
+ emsg(_("E260: Missing name after ->"));
+ return FAIL;
+ }
+
+ // Check for the "(". Skip over white space after it.
+ if (name[len] != '(')
+ {
+ if (verbose)
+ semsg(_(e_missingparen), name);
+ return FAIL;
+ }
+ *arg += len;
+
+ vim_memset(&funcexe, 0, sizeof(funcexe));
+ funcexe.evaluate = evaluate;
+ funcexe.basetv = &base;
+ rettv->v_type = VAR_UNKNOWN;
+ ret = get_func_tv(name, len, rettv, arg, &funcexe);
+
+ /* Clear the funcref afterwards, so that deleting it while
+ * evaluating the arguments is possible (see test55). */
+ if (evaluate)
+ clear_tv(&base);
+
+ /* Stop the expression evaluation when immediately aborting on
+ * error, or when an interrupt occurred or an exception was thrown
+ * but not caught. */
+ if (aborting())
+ {
+ if (ret == OK)
+ clear_tv(rettv);
+ ret = FAIL;
+ }
+ return ret;
+ }
+
+ /*
* Evaluate an "[expr]" or "[expr:expr]" index. Also "dict.key".
* "*arg" points to the '[' or '.'.
* Returns FAIL or OK. "*arg" is advanced to after the ']'.
***************
*** 7359,7367 ****
}

/*
! * 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)
*/
int
handle_subscript(
--- 7423,7435 ----
}

/*
! * Handle:
! * - expr[expr], expr[expr:expr] subscript
! * - ".name" lookup
! * - function call with Funcref variable: func(expr)
! * - method call: var->method()
! *
! * Can all be combined in any order: dict.func(expr)[idx]['func'](expr)->len()
*/
int
handle_subscript(
***************
*** 7378,7391 ****
// "." is ".name" lookup when we found a dict or when evaluating and
// scriptversion is at least 2, where string concatenation is "..".
while (ret == OK
! && (**arg == '['
! || (**arg == '.' && (rettv->v_type == VAR_DICT
|| (!evaluate
&& (*arg)[1] != '.'
&& current_sctx.sc_version >= 2)))
! || (**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC
|| rettv->v_type == VAR_PARTIAL)))
! && !VIM_ISWHITE(*(*arg - 1)))
{
if (**arg == '(')
{
--- 7446,7460 ----
// "." is ".name" lookup when we found a dict or when evaluating and
// scriptversion is at least 2, where string concatenation is "..".
while (ret == OK
! && (((**arg == '['
! || (**arg == '.' && (rettv->v_type == VAR_DICT
|| (!evaluate
&& (*arg)[1] != '.'
&& current_sctx.sc_version >= 2)))
! || (**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC
|| rettv->v_type == VAR_PARTIAL)))
! && !VIM_ISWHITE(*(*arg - 1)))
! || (**arg == '-' && (*arg)[1] == '>')))
{
if (**arg == '(')
{
***************
*** 7410,7419 ****
else
s = (char_u *)"";

! funcexe.argv_func = NULL;
funcexe.firstline = curwin->w_cursor.lnum;
funcexe.lastline = curwin->w_cursor.lnum;
- funcexe.doesrange = NULL;
funcexe.evaluate = evaluate;
funcexe.partial = pt;
funcexe.selfdict = selfdict;
--- 7479,7487 ----
else
s = (char_u *)"";

! vim_memset(&funcexe, 0, sizeof(funcexe));
funcexe.firstline = curwin->w_cursor.lnum;
funcexe.lastline = curwin->w_cursor.lnum;
funcexe.evaluate = evaluate;
funcexe.partial = pt;
funcexe.selfdict = selfdict;
***************
*** 7436,7441 ****
--- 7504,7517 ----
dict_unref(selfdict);
selfdict = NULL;
}
+ else if (**arg == '-')
+ {
+ if (eval_method(arg, rettv, evaluate, verbose) == FAIL)
+ {
+ clear_tv(rettv);
+ ret = FAIL;
+ }
+ }
else /* **arg == '[' || **arg == '.' */
{
dict_unref(selfdict);
*** ../vim-8.1.1802/src/structs.h 2019-08-03 18:28:13.871145539 +0200
--- src/structs.h 2019-08-03 18:48:44.098802528 +0200
***************
*** 1619,1624 ****
--- 1619,1625 ----
int evaluate; // actually evaluate expressions
partial_T *partial; // for extra arguments
dict_T *selfdict; // Dictionary for "self"
+ typval_T *basetv; // base for base->method()
} funcexe_T;

struct partial_S
*** ../vim-8.1.1802/src/userfunc.c 2019-08-03 18:17:07.680638632 +0200
--- src/userfunc.c 2019-08-03 19:12:12.720843818 +0200
***************
*** 1431,1440 ****
{
funcexe_T funcexe;

! funcexe.argv_func = NULL;
funcexe.firstline = curwin->w_cursor.lnum;
funcexe.lastline = curwin->w_cursor.lnum;
- funcexe.doesrange = NULL;
funcexe.evaluate = TRUE;
funcexe.partial = partial;
funcexe.selfdict = selfdict;
--- 1431,1439 ----
{
funcexe_T funcexe;

! vim_memset(&funcexe, 0, sizeof(funcexe));
funcexe.firstline = curwin->w_cursor.lnum;
funcexe.lastline = curwin->w_cursor.lnum;
funcexe.evaluate = TRUE;
funcexe.partial = partial;
funcexe.selfdict = selfdict;
***************
*** 1555,1561 ****
/*
* User defined function.
*/
! if (partial != NULL && partial->pt_func != NULL)
fp = partial->pt_func;
else
fp = find_func(rfname);
--- 1554,1563 ----
/*
* User defined function.
*/
! if (funcexe->basetv != NULL)
! // TODO: support User function: base->Method()
! fp = NULL;
! else if (partial != NULL && partial->pt_func != NULL)
fp = partial->pt_func;
else
fp = find_func(rfname);
***************
*** 1625,1630 ****
--- 1627,1640 ----
}
}
}
+ else if (funcexe->basetv != NULL)
+ {
+ /*
+ * Find the method name in the table, call its implementation.
+ */
+ error = call_internal_method(fname, argcount, argvars, rettv,
+ funcexe->basetv);
+ }
else
{
/*
***************
*** 2715,2721 ****
translated_function_exists(char_u *name)
{
if (builtin_function(name, -1))
! return find_internal_func(name) >= 0;
return find_func(name) != NULL;
}

--- 2725,2731 ----
translated_function_exists(char_u *name)
{
if (builtin_function(name, -1))
! return has_internal_func(name);
return find_func(name) != NULL;
}

***************
*** 3084,3090 ****

if (*startarg != '(')
{
! semsg(_("E107: Missing parentheses: %s"), eap->arg);
goto end;
}

--- 3094,3100 ----

if (*startarg != '(')
{
! semsg(_(e_missingparen), eap->arg);
goto end;
}

***************
*** 3120,3126 ****
}
arg = startarg;

! funcexe.argv_func = NULL;
funcexe.firstline = eap->line1;
funcexe.lastline = eap->line2;
funcexe.doesrange = &doesrange;
--- 3130,3136 ----
}
arg = startarg;

! vim_memset(&funcexe, 0, sizeof(funcexe));
funcexe.firstline = eap->line1;
funcexe.lastline = eap->line2;
funcexe.doesrange = &doesrange;
*** ../vim-8.1.1802/src/globals.h 2019-08-02 22:08:21.201361921 +0200
--- src/globals.h 2019-08-03 18:58:05.118632248 +0200
***************
*** 1592,1597 ****
--- 1592,1598 ----
EXTERN char e_zerocount[] INIT(= N_("E939: Positive count required"));
#ifdef FEAT_EVAL
EXTERN char e_usingsid[] INIT(= N_("E81: Using <SID> not in a script context"));
+ EXTERN char e_missingparen[] INIT(= N_("E107: Missing parentheses: %s"));
#endif
#ifdef FEAT_CLIENTSERVER
EXTERN char e_invexprmsg[] INIT(= N_("E449: Invalid expression received"));
*** ../vim-8.1.1802/src/evalfunc.c 2019-08-02 19:52:12.017753693 +0200
--- src/evalfunc.c 2019-08-03 20:15:28.254662009 +0200
***************
*** 412,425 ****
* Array with names and number of arguments of all internal functions
* MUST BE KEPT SORTED IN strcmp() ORDER FOR BINARY SEARCH!
*/
! static struct fst
{
char *f_name; /* function name */
char f_min_argc; /* minimal number of arguments */
char f_max_argc; /* maximal number of arguments */
void (*f_func)(typval_T *args, typval_T *rvar);
/* implementation of function */
! } functions[] =
{
#ifdef FEAT_FLOAT
{"abs", 1, 1, f_abs},
--- 412,427 ----
* Array with names and number of arguments of all internal functions
* MUST BE KEPT SORTED IN strcmp() ORDER FOR BINARY SEARCH!
*/
! typedef struct
{
char *f_name; /* function name */
char f_min_argc; /* minimal number of arguments */
char f_max_argc; /* maximal number of arguments */
void (*f_func)(typval_T *args, typval_T *rvar);
/* implementation of function */
! } funcentry_T;
!
! static funcentry_T global_functions[] =
{
#ifdef FEAT_FLOAT
{"abs", 1, 1, f_abs},
***************
*** 987,992 ****
--- 989,1025 ----
{"xor", 2, 2, f_xor},
};

+ /*
+ * Methods that call the internal function with the base as the first argument.
+ */
+ static funcentry_T base_methods[] =
+ {
+ {"add", 1, 1, f_add},
+ {"copy", 0, 0, f_copy},
+ {"count", 1, 3, f_count},
+ {"empty", 0, 0, f_empty},
+ {"extend", 1, 2, f_extend},
+ {"filter", 1, 1, f_filter},
+ {"get", 1, 2, f_get},
+ {"index", 1, 3, f_index},
+ {"insert", 1, 2, f_insert},
+ {"items", 0, 0, f_items},
+ {"join", 0, 1, f_join},
+ {"keys", 0, 0, f_keys},
+ {"len", 0, 0, f_len},
+ {"map", 1, 1, f_map},
+ {"max", 0, 0, f_max},
+ {"min", 0, 0, f_min},
+ {"remove", 1, 2, f_remove},
+ {"repeat", 1, 1, f_repeat},
+ {"reverse", 0, 0, f_reverse},
+ {"sort", 0, 2, f_sort},
+ {"string", 0, 0, f_string},
+ {"type", 0, 0, f_type},
+ {"uniq", 0, 2, f_uniq},
+ {"values", 0, 0, f_values},
+ };
+
#if defined(FEAT_CMDL_COMPL) || defined(PROTO)

/*
***************
*** 1007,1017 ****
if (name != NULL)
return name;
}
! if (++intidx < (int)(sizeof(functions) / sizeof(struct fst)))
{
! STRCPY(IObuff, functions[intidx].f_name);
STRCAT(IObuff, "(");
! if (functions[intidx].f_max_argc == 0)
STRCAT(IObuff, ")");
return IObuff;
}
--- 1040,1050 ----
if (name != NULL)
return name;
}
! if (++intidx < (int)(sizeof(global_functions) / sizeof(funcentry_T)))
{
! STRCPY(IObuff, global_functions[intidx].f_name);
STRCAT(IObuff, "(");
! if (global_functions[intidx].f_max_argc == 0)
STRCAT(IObuff, ")");
return IObuff;
}
***************
*** 1043,1063 ****
#endif /* FEAT_CMDL_COMPL */

/*
! * Find internal function in table above.
* Return index, or -1 if not found
*/
! int
find_internal_func(
! char_u *name) /* name of the function */
{
int first = 0;
! int last = (int)(sizeof(functions) / sizeof(struct fst)) - 1;
int cmp;
int x;

! /*
! * Find the function name in the table. Binary search.
! */
while (first <= last)
{
x = first + ((unsigned)(last - first) >> 1);
--- 1076,1100 ----
#endif /* FEAT_CMDL_COMPL */

/*
! * Find internal function in table "functions".
* Return index, or -1 if not found
*/
! static int
find_internal_func(
! char_u *name, // name of the function
! funcentry_T *functions) // functions table to use
{
int first = 0;
! int last;
int cmp;
int x;

! if (functions == global_functions)
! last = (int)(sizeof(global_functions) / sizeof(funcentry_T)) - 1;
! else
! last = (int)(sizeof(base_methods) / sizeof(funcentry_T)) - 1;
!
! // Find the function name in the table. Binary search.
while (first <= last)
{
x = first + ((unsigned)(last - first) >> 1);
***************
*** 1073,1078 ****
--- 1110,1121 ----
}

int
+ has_internal_func(char_u *name)
+ {
+ return find_internal_func(name, global_functions) >= 0;
+ }
+
+ int
call_internal_func(
char_u *name,
int argcount,
***************
*** 1081,1095 ****
{
int i;

! i = find_internal_func(name);
if (i < 0)
return ERROR_UNKNOWN;
! if (argcount < functions[i].f_min_argc)
return ERROR_TOOFEW;
! if (argcount > functions[i].f_max_argc)
return ERROR_TOOMANY;
argvars[argcount].v_type = VAR_UNKNOWN;
! functions[i].f_func(argvars, rettv);
return ERROR_NONE;
}

--- 1124,1170 ----
{
int i;

! i = find_internal_func(name, global_functions);
if (i < 0)
return ERROR_UNKNOWN;
! if (argcount < global_functions[i].f_min_argc)
return ERROR_TOOFEW;
! if (argcount > global_functions[i].f_max_argc)
return ERROR_TOOMANY;
argvars[argcount].v_type = VAR_UNKNOWN;
! 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;
}

*** ../vim-8.1.1802/src/proto/evalfunc.pro 2019-07-22 23:03:53.322360395 +0200
--- src/proto/evalfunc.pro 2019-08-03 19:26:13.219180538 +0200
***************
*** 1,8 ****
/* evalfunc.c */
char_u *get_function_name(expand_T *xp, int idx);
char_u *get_expr_name(expand_T *xp, int idx);
! int find_internal_func(char_u *name);
int call_internal_func(char_u *name, int argcount, typval_T *argvars, typval_T *rettv);
linenr_T tv_get_lnum(typval_T *argvars);
buf_T *buflist_find_by_name(char_u *name, int curtab_only);
buf_T *tv_get_buf(typval_T *tv, int curtab_only);
--- 1,9 ----
/* evalfunc.c */
char_u *get_function_name(expand_T *xp, int idx);
char_u *get_expr_name(expand_T *xp, int idx);
! int has_internal_func(char_u *name);
int call_internal_func(char_u *name, int argcount, typval_T *argvars, typval_T *rettv);
+ int call_internal_method(char_u *name, int argcount, typval_T *argvars, typval_T *rettv, typval_T *basetv);
linenr_T tv_get_lnum(typval_T *argvars);
buf_T *buflist_find_by_name(char_u *name, int curtab_only);
buf_T *tv_get_buf(typval_T *tv, int curtab_only);
*** ../vim-8.1.1802/src/testdir/test_method.vim 2019-08-03 21:55:41.170573238 +0200
--- src/testdir/test_method.vim 2019-08-03 20:49:18.095540067 +0200
***************
*** 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
*** ../vim-8.1.1802/src/testdir/Make_all.mak 2019-07-24 22:30:06.336638707 +0200
--- src/testdir/Make_all.mak 2019-08-03 20:00:39.246433684 +0200
***************
*** 180,185 ****
--- 180,186 ----
test_matchadd_conceal \
test_matchadd_conceal_utf8 \
test_memory_usage \
+ test_method \
test_menu \
test_messages \
test_mksession \
***************
*** 373,378 ****
--- 374,380 ----
test_marks.res \
test_matchadd_conceal.res \
test_memory_usage.res \
+ test_method.res \
test_mksession.res \
test_nested_function.res \
test_netbeans.res \
*** ../vim-8.1.1802/src/version.c 2019-08-03 18:31:08.313893064 +0200
--- src/version.c 2019-08-03 20:53:02.070279207 +0200
***************
*** 775,776 ****
--- 775,778 ----
{ /* Add new patch number below this line */
+ /**/
+ 1803,
/**/

--
hundred-and-one symptoms of being an internet addict:
20. When looking at a pageful of someone else's links, you notice all of them
are already highlighted in purple.

/// Bram Moolenaar -- Br...@Moolenaar.net -- http://www.Moolenaar.net \\\
/// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\ an exciting new programming language -- http://www.Zimbu.org ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///

Bram Moolenaar

unread,
Aug 3, 2019, 4:29:35 PM8/3/19
to vim...@googlegroups.com

I wrote:

> Patch 8.1.1803
> Problem: All builtin functions are global.
> Solution: Add the method call operator ->. Implemented for a limited number
> of functions.
> Files: runtime/doc/eval.txt, src/eval.c, src/structs.h, src/userfunc.c,
> src/globals.h, src/evalfunc.c, src/proto/evalfunc.pro,
> src/testdir/test_method.vim, src/testdir/Make_all.mak


For now this makes a few existing functions available as methods, e.g.

mylist->filter(filterexpr)->map(mapexpr)->sort()->join()

It's a lot easier to read than:

join(sort(map(filter(mylist, filterexpr), mapexpr)))

When we add new methods for List, Dict, etc. we can now make them local
to those objects, instead of adding them globally.

I decided to use "->" because of the similarity with C pointer usage.
It would be nice to use ".", but I'm quite sure that won't be backwards
compatible, especially when using a Dict.

Comments are welcome!

--
hundred-and-one symptoms of being an internet addict:
21. Your dog has its own home page.

Andy Massimino

unread,
Aug 3, 2019, 8:32:56 PM8/3/19
to vim...@googlegroups.com, Bram Moolenaar
On 8/3/19 4:29 PM, Bram Moolenaar wrote:
> I wrote:
>
>> Patch 8.1.1803
>> Problem: All builtin functions are global.
>> Solution: Add the method call operator ->. Implemented for a limited number
>> of functions.
>> Files: runtime/doc/eval.txt, src/eval.c, src/structs.h, src/userfunc.c,
>> src/globals.h, src/evalfunc.c, src/proto/evalfunc.pro,
>> src/testdir/test_method.vim, src/testdir/Make_all.mak
>
> For now this makes a few existing functions available as methods, e.g.
>
> mylist->filter(filterexpr)->map(mapexpr)->sort()->join()
>
> It's a lot easier to read than:
>
> join(sort(map(filter(mylist, filterexpr), mapexpr)))
>
> When we add new methods for List, Dict, etc. we can now make them local
> to those objects, instead of adding them globally.
>
> I decided to use "->" because of the similarity with C pointer usage.
> It would be nice to use ".", but I'm quite sure that won't be backwards
> compatible, especially when using a Dict.

I agree, backwards compatibility should not be broken.

But I wonder if "." is fine with scriptversion 2.  If the dict has a
field named
"len" then it will be used as before, otherwise the len() function will
be called.
This means users can also override the methods if they choose, but no
existing
scripts would be broken.

>
> Comments are welcome!
>

Bram Moolenaar

unread,
Aug 4, 2019, 9:04:59 AM8/4/19
to vim...@googlegroups.com, Andy Massimino, Bram Moolenaar
Maybe it's possible to use "." in some situations, but consistency is
also important. It's also clear that ->name( is a method call.

Still need to decide what is the best way to define user methods.

--
Computers are useless. They can only give you answers.
-- Pablo Picasso
Reply all
Reply to author
Forward
0 new messages