Compare commits

...

11 Commits

Author SHA1 Message Date
3a53ec8bdd patch 8.2.1330: Github workflow takes longer than needed
Problem:    Github workflow takes longer than needed.
Solution:   Do two test runs in parallel instead of sequentially. (Ken Takata,
            closes #6579)
2020-07-31 22:17:32 +02:00
38ddf333f6 patch 8.2.1329: Vim9: cannot define global function inside :def function
Problem:    Vim9: cannot define global function inside :def function.
Solution:   Assign to global variable instead of local. (closes #6584)
2020-07-31 22:05:04 +02:00
4d4d1cd5c8 patch 8.2.1328: no space allowed before comma in list
Problem:    No space allowed before comma in list.
Solution:   Legacy Vim script allows it. (closes #6577)
2020-07-30 22:14:33 +02:00
f4ee528086 patch 8.2.1327: Mac: configure can't find Tcl libraries
Problem:    Mac: configure can't find Tcl libraries.
Solution:   Adjust configure check. (closes #6575)
2020-07-30 20:18:08 +02:00
9d48956681 patch 8.2.1326: Vim9: skipping over white space after list
Problem:    Vim9: skipping over white space after list.
Solution:   Do not skip white space, a following [] would be misinterpreted.
            (closes #6552)  Fix a few side effects.
2020-07-30 20:08:50 +02:00
ea2d8d2571 patch 8.2.1325: Vim9: using Vim9 script for autaload not tested
Problem:    Vim9: using Vim9 script for autaload not tested.
Solution:   Add a test.  Update help.
2020-07-29 22:11:05 +02:00
c7e44a7e4c patch 8.2.1324: Vim9: line break after "=" does not work
Problem:    Vim9: line break after "=" does not work.
Solution:   Also allow for NUL after "=". (closes #6549)
2020-07-29 21:37:43 +02:00
696ba23149 patch 8.2.1323: Vim9: invalid operators only rejected in :def function
Problem:    Vim9: invalid operators only rejected in :def function.
Solution:   Also reject them at script level. (closes #6564)
2020-07-29 21:20:41 +02:00
1040956292 patch 8.2.1322: Vim9: method on double quoted string doesn't work
Problem:    Vim9: method on double quoted string doesn't work.
Solution:   Recognize double quoted string. (closes #6562)
2020-07-29 20:00:38 +02:00
7b7f78f51d patch 8.2.1321: GitHub CI also runs on tag push
Problem:    GitHub CI also runs on tag push.
Solution:   Skip CI on push. (Ken Takata, closes #6571)
2020-07-29 19:29:23 +02:00
33afa2447b patch 8.2.1320: Vim9: cannot declare some single letter variables
Problem:    Vim9: cannot declare some single letter variables.
Solution:   Do not recognize a colon for a namespace for single letter
            variables. (closes #6547)
2020-07-29 19:18:00 +02:00
32 changed files with 496 additions and 120 deletions

View File

@ -2,6 +2,8 @@ name: GitHub CI
on:
push:
branches:
- '*'
pull_request:
env:
@ -193,16 +195,29 @@ jobs:
echo.
echo %COL_GREEN%vim version:%COL_RESET%
.\vim --version || exit 1
cd testdir
mkdir ..\src2
xcopy testdir ..\src2\testdir\ /E > nul || exit 1
copy evalfunc.c ..\src2 > nul
echo %COL_GREEN%Start testing vim in background.%COL_RESET%
start cmd /c "cd ..\src2\testdir & nmake -nologo -f Make_dos.mak VIMPROG=..\..\src\vim > nul & echo done>done.txt"
echo %COL_GREEN%Test gvim:%COL_RESET%
cd testdir
nmake -nologo -f Make_dos.mak VIMPROG=..\gvim || exit 1
nmake -nologo -f Make_dos.mak clean
echo %COL_GREEN%Test vim:%COL_RESET%
if "${{ matrix.toolchain }}-${{ matrix.arch }}"=="msvc-x64" (
rem This test may hang up unless it is executed in a separate console.
start /wait cmd /c "nmake -nologo -f Make_dos.mak VIMPROG=..\vim > nul"
if exist messages type messages
nmake -nologo -f Make_dos.mak report || exit 1
) else (
nmake -nologo -f Make_dos.mak VIMPROG=..\vim || exit 1
cd ..
echo %COL_GREEN%Wait for vim tests to finish.%COL_RESET%
cd ..\src2\testdir
:: Wait about 5 minutes.
for /L %%i in (1,1,300) do (
if exist done.txt goto exitloop
ping -n 2 localhost > nul
)
echo %COL_RED%Timed out.%COL_RESET%
:exitloop
echo %COL_GREEN%Test results of vim:%COL_RESET%
if exist messages type messages
nmake -nologo -f Make_dos.mak report VIMPROG=..\..\src\vim || exit 1

View File

@ -118,9 +118,13 @@ Functions and variables are script-local by default ~
*vim9-scopes*
When using `:function` or `:def` to specify a new function at the script level
in a Vim9 script, the function is local to the script, as if "s:" was
prefixed. Using the "s:" prefix is optional.
To define or use a global function or variable the "g:" prefix must be used.
prefixed. Using the "s:" prefix is optional. To define or use a global
function or variable the "g:" prefix must be used. For functions in an
autoload script the "name#" prefix is sufficient. >
def ThisFunction() # script-local
def s:ThisFunction() # script-local
def g:ThatFunction() # global
def scriptname#function() # autoload
When using `:function` or `:def` to specify a new function inside a function,
the function is local to the function. It is not possible to define a

5
src/auto/configure vendored
View File

@ -7417,7 +7417,7 @@ $as_echo_n "checking for location of Tcl include... " >&6; }
if test "x$MACOS_X" != "xyes"; then
tclinc="$tclloc/include $tclloc/include/tcl $tclloc/include/tcl$tclver /usr/local/include /usr/local/include/tcl$tclver /usr/include /usr/include/tcl$tclver"
else
tclinc="/System/Library/Frameworks/Tcl.framework/Headers"
tclinc="$tclloc/include $tclloc/include/tcl $tclloc/include/tcl$tclver /System/Library/Frameworks/Tcl.framework/Headers `xcrun --show-sdk-path`/System/Library/Frameworks/Tcl.framework/Versions/Current/Headers"
fi
TCL_INC=
for try in $tclinc; do
@ -7440,7 +7440,8 @@ $as_echo_n "checking for location of tclConfig.sh script... " >&6; }
tclcnf=`echo $tclinc | sed s/include/lib/g`
tclcnf="$tclcnf `echo $tclinc | sed s/include/lib64/g`"
else
tclcnf="/System/Library/Frameworks/Tcl.framework"
tclcnf=`echo $tclinc | sed s/include/lib/g`
tclcnf="$tclcnf /System/Library/Frameworks/Tcl.framework `xcrun --show-sdk-path`/System/Library/Frameworks/Tcl.framework"
fi
for try in $tclcnf; do
if test -f "$try/tclConfig.sh"; then

View File

@ -1820,8 +1820,10 @@ if test "$enable_tclinterp" = "yes" -o "$enable_tclinterp" = "dynamic"; then
if test "x$MACOS_X" != "xyes"; then
tclinc="$tclloc/include $tclloc/include/tcl $tclloc/include/tcl$tclver /usr/local/include /usr/local/include/tcl$tclver /usr/include /usr/include/tcl$tclver"
else
dnl For all macOS, use the value from TCL in case use of, say, homebrew
dnl For Mac OS X 10.3, use the OS-provided framework location
tclinc="/System/Library/Frameworks/Tcl.framework/Headers"
dnl For Mac OS X 10.14, the OS-provided framework location doesn't contain the headers, so also check the Xcode SDK
tclinc="$tclloc/include $tclloc/include/tcl $tclloc/include/tcl$tclver /System/Library/Frameworks/Tcl.framework/Headers `xcrun --show-sdk-path`/System/Library/Frameworks/Tcl.framework/Versions/Current/Headers"
fi
TCL_INC=
for try in $tclinc; do
@ -1841,8 +1843,11 @@ if test "$enable_tclinterp" = "yes" -o "$enable_tclinterp" = "dynamic"; then
tclcnf=`echo $tclinc | sed s/include/lib/g`
tclcnf="$tclcnf `echo $tclinc | sed s/include/lib64/g`"
else
dnl For all macOS, use the value from TCL in case use of, say, homebrew
dnl For Mac OS X 10.3, use the OS-provided framework location
tclcnf="/System/Library/Frameworks/Tcl.framework"
dnl For Mac OS X 10.14, the OS-provided framework location doesn't contain the headers, so also check the Xcode SDK
tclcnf=`echo $tclinc | sed s/include/lib/g`
tclcnf="$tclcnf /System/Library/Frameworks/Tcl.framework `xcrun --show-sdk-path`/System/Library/Frameworks/Tcl.framework"
fi
for try in $tclcnf; do
if test -f "$try/tclConfig.sh"; then

View File

@ -838,6 +838,10 @@ eval_dict(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int literal)
: eval1(arg, &tvkey, evalarg)) == FAIL) // recursive!
goto failret;
// the colon should come right after the key, but this wasn't checked
// previously, so only require it in Vim9 script.
if (!vim9script)
*arg = skipwhite(*arg);
if (**arg != ':')
{
if (evaluate)
@ -891,7 +895,10 @@ eval_dict(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int literal)
}
clear_tv(&tvkey);
// the comma must come after the value
// the comma should come right after the value, but this wasn't checked
// previously, so only require it in Vim9 script.
if (!vim9script)
*arg = skipwhite(*arg);
had_comma = **arg == ',';
if (had_comma)
{

View File

@ -1913,27 +1913,28 @@ eval_func(
char_u *
eval_next_non_blank(char_u *arg, evalarg_T *evalarg, int *getnext)
{
char_u *p = skipwhite(arg);
*getnext = FALSE;
if (in_vim9script()
&& evalarg != NULL
&& (evalarg->eval_cookie != NULL || evalarg->eval_cctx != NULL)
&& (*arg == NUL || (VIM_ISWHITE(arg[-1])
&& vim9_comment_start(arg))))
&& (*p == NUL || (VIM_ISWHITE(p[-1]) && vim9_comment_start(p))))
{
char_u *p;
char_u *next;
if (evalarg->eval_cookie != NULL)
p = getline_peek(evalarg->eval_getline, evalarg->eval_cookie);
next = getline_peek(evalarg->eval_getline, evalarg->eval_cookie);
else
p = peek_next_line_from_context(evalarg->eval_cctx);
next = peek_next_line_from_context(evalarg->eval_cctx);
if (p != NULL)
if (next != NULL)
{
*getnext = TRUE;
return skipwhite(p);
return skipwhite(next);
}
}
return arg;
return p;
}
/*
@ -2039,6 +2040,7 @@ eval0(
p = skipwhite(arg);
ret = eval1(&p, rettv, evalarg);
p = skipwhite(p);
if (ret == FAIL || !ends_excmd2(arg, p))
{
@ -2107,6 +2109,8 @@ eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
if (getnext)
*arg = eval_next_line(evalarg_used);
else
*arg = p;
result = FALSE;
if (evaluate)
@ -2142,6 +2146,8 @@ eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
}
if (getnext)
*arg = eval_next_line(evalarg_used);
else
*arg = p;
/*
* Get the third variable. Recursive!
@ -2234,6 +2240,8 @@ eval2(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
{
if (getnext)
*arg = eval_next_line(evalarg_used);
else
*arg = p;
/*
* Get the second variable.
@ -2349,6 +2357,8 @@ eval3(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
{
if (getnext)
*arg = eval_next_line(evalarg_used);
else
*arg = p;
/*
* Get the second variable.
@ -2420,9 +2430,9 @@ eval4(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
{
char_u *p;
int getnext;
int i;
exptype_T type = EXPR_UNKNOWN;
int len = 2;
int type_is = FALSE;
/*
* Get the first variable.
@ -2431,44 +2441,7 @@ eval4(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
return FAIL;
p = eval_next_non_blank(*arg, evalarg, &getnext);
switch (p[0])
{
case '=': if (p[1] == '=')
type = EXPR_EQUAL;
else if (p[1] == '~')
type = EXPR_MATCH;
break;
case '!': if (p[1] == '=')
type = EXPR_NEQUAL;
else if (p[1] == '~')
type = EXPR_NOMATCH;
break;
case '>': if (p[1] != '=')
{
type = EXPR_GREATER;
len = 1;
}
else
type = EXPR_GEQUAL;
break;
case '<': if (p[1] != '=')
{
type = EXPR_SMALLER;
len = 1;
}
else
type = EXPR_SEQUAL;
break;
case 'i': if (p[1] == 's')
{
if (p[2] == 'n' && p[3] == 'o' && p[4] == 't')
len = 5;
i = p[len];
if (!isalnum(i) && i != '_')
type = len == 2 ? EXPR_IS : EXPR_ISNOT;
}
break;
}
type = get_compare_type(p, &len, &type_is);
/*
* If there is a comparative operator, use it.
@ -2482,6 +2455,13 @@ eval4(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
if (getnext)
*arg = eval_next_line(evalarg);
if (vim9script && type_is && (p[len] == '?' || p[len] == '#'))
{
semsg(_(e_invexpr2), p);
clear_tv(rettv);
return FAIL;
}
// extra question mark appended: ignore case
if (p[len] == '?')
{
@ -2605,6 +2585,8 @@ eval5(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
if (getnext)
*arg = eval_next_line(evalarg);
else
*arg = p;
evaluate = evalarg == NULL ? 0 : (evalarg->eval_flags & EVAL_EVALUATE);
if ((op != '+' || (rettv->v_type != VAR_LIST
&& rettv->v_type != VAR_BLOB))
@ -2786,6 +2768,7 @@ eval6(
int evaluate;
int getnext;
typval_T var2;
char_u *p;
int op;
varnumber_T n1, n2;
#ifdef FEAT_FLOAT
@ -2793,12 +2776,15 @@ eval6(
#endif
int error;
op = *eval_next_non_blank(*arg, evalarg, &getnext);
p = eval_next_non_blank(*arg, evalarg, &getnext);
op = *p;
if (op != '*' && op != '/' && op != '%')
break;
if (getnext)
*arg = eval_next_line(evalarg);
else
*arg = p;
#ifdef FEAT_FLOAT
f1 = 0;
@ -3145,8 +3131,6 @@ eval7(
vim_free(alias);
}
*arg = skipwhite(*arg);
// Handle following '[', '(' and '.' for expr[expr], expr.name,
// expr(expr), expr->name(expr)
if (ret == OK)
@ -5182,7 +5166,7 @@ handle_subscript(
p = eval_next_non_blank(*arg, evalarg, &getnext);
if (getnext
&& ((rettv->v_type == VAR_DICT && *p == '.' && eval_isdictc(p[1]))
|| (*p == '-' && p[1] == '>'
|| (p[0] == '-' && p[1] == '>'
&& (p[2] == '{' || ASCII_ISALPHA(p[2])))))
{
*arg = eval_next_line(evalarg);
@ -5208,8 +5192,9 @@ handle_subscript(
dict_unref(selfdict);
selfdict = NULL;
}
else if (**arg == '-' && (*arg)[1] == '>')
else if (p[0] == '-' && p[1] == '>')
{
*arg = p;
if (ret == OK)
{
if ((*arg)[2] == '{')

View File

@ -801,13 +801,13 @@ ex_let(exarg_T *eap)
else
++expr;
if (vim9script && (!VIM_ISWHITE(*argend) || !VIM_ISWHITE(*expr)))
if (vim9script && (!VIM_ISWHITE(*argend)
|| !IS_WHITE_OR_NUL(*expr)))
{
vim_strncpy(op, expr - len, len);
semsg(_(e_white_both), op);
i = FAIL;
}
expr = skipwhite(expr);
if (eap->skip)
++emsg_skip;
@ -818,6 +818,7 @@ ex_let(exarg_T *eap)
evalarg.eval_getline = eap->getline;
evalarg.eval_cookie = eap->cookie;
}
expr = skipwhite_and_linebreak(expr, &evalarg);
i = eval0(expr, &rettv, eap, &evalarg);
if (eap->skip)
--emsg_skip;

View File

@ -3277,7 +3277,7 @@ find_ex_command(
char_u *pskip = (*eap->cmd == '&' || *eap->cmd == '$'
|| *eap->cmd == '@') ? eap->cmd + 1 : eap->cmd;
if (vim_strchr((char_u *)"{('[", *p) != NULL
if (vim_strchr((char_u *)"{('[\"", *p) != NULL
|| ((p = to_name_const_end(pskip)) > eap->cmd && *p != NUL))
{
int oplen;
@ -3293,6 +3293,8 @@ find_ex_command(
*eap->cmd == '{'
// "'string'->func()" is an expression.
|| *eap->cmd == '\''
// '"string"->func()' is an expression.
|| *eap->cmd == '"'
// "g:varname" is an expression.
|| eap->cmd[1] == ':'
)

View File

@ -1194,12 +1194,15 @@ eval_list(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int do_error)
else
clear_tv(&tv);
}
// Legacy Vim script allowed a space before the comma.
if (!vim9script)
*arg = skipwhite(*arg);
// the comma must come after the value
had_comma = **arg == ',';
if (had_comma)
{
if (vim9script && (*arg)[1] != NUL && !VIM_ISWHITE((*arg)[1]))
if (vim9script && !IS_WHITE_OR_NUL((*arg)[1]))
{
semsg(_(e_white_after), ",");
goto failret;
@ -1231,7 +1234,7 @@ failret:
return FAIL;
}
*arg = skipwhite(*arg + 1);
*arg += 1;
if (evaluate)
rettv_list_set(rettv, l);

View File

@ -2027,6 +2027,41 @@ ga_clear_strings(garray_T *gap)
ga_clear(gap);
}
/*
* Copy a growing array that contains a list of strings.
*/
int
ga_copy_strings(garray_T *from, garray_T *to)
{
int i;
ga_init2(to, sizeof(char_u *), 1);
if (ga_grow(to, from->ga_len) == FAIL)
return FAIL;
for (i = 0; i < from->ga_len; ++i)
{
char_u *orig = ((char_u **)from->ga_data)[i];
char_u *copy;
if (orig == NULL)
copy = NULL;
else
{
copy = vim_strsave(orig);
if (copy == NULL)
{
to->ga_len = i;
ga_clear_strings(to);
return FAIL;
}
}
((char_u **)to->ga_data)[i] = copy;
}
to->ga_len = from->ga_len;
return OK;
}
/*
* Initialize a growing array. Don't forget to set ga_itemsize and
* ga_growsize! Or use ga_init2().

View File

@ -56,6 +56,7 @@ char_u *vim_strrchr(char_u *string, int c);
int vim_isspace(int x);
void ga_clear(garray_T *gap);
void ga_clear_strings(garray_T *gap);
int ga_copy_strings(garray_T *from, garray_T *to);
void ga_init(garray_T *gap);
void ga_init2(garray_T *gap, int itemsize, int growsize);
int ga_grow(garray_T *gap, int n);

View File

@ -5,6 +5,7 @@ int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, garray_T
char_u *get_lambda_name(void);
char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state);
int get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg);
void copy_func(char_u *lambda, char_u *global);
char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload);
void emsg_funcname(char *ermsg, char_u *name);
int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, evalarg_T *evalarg, funcexe_T *funcexe);

View File

@ -16,6 +16,7 @@ int vim9_comment_start(char_u *p);
char_u *peek_next_line_from_context(cctx_T *cctx);
char_u *next_line_from_context(cctx_T *cctx, int skip_comment);
char_u *to_name_const_end(char_u *arg);
exptype_T get_compare_type(char_u *p, int *len, int *type_is);
int assignment_len(char_u *p, int *heredoc);
void vim9_declare_error(char_u *name);
int check_vim9_unlet(char_u *name);

View File

@ -1546,6 +1546,7 @@ typedef enum {
/*
* Structure to hold info for a user function.
* When adding a field check copy_func().
*/
typedef struct
{
@ -1618,6 +1619,7 @@ typedef struct
#define FC_NOARGS 0x200 // no a: variables in lambda
#define FC_VIM9 0x400 // defined in vim9 script file
#define FC_CFUNC 0x800 // defined as Lua C func
#define FC_COPY 0x1000 // copy of another function by copy_func()
#define MAX_FUNC_ARGS 20 // maximum number of function arguments
#define VAR_SHORT_LEN 20 // short variable name length

View File

@ -0,0 +1,9 @@
vim9script
func auto9#getsome()
return 'some'
endfunc
def auto9#add42(count: number): number
return count + 42
enddef

View File

@ -17,3 +17,8 @@ func Test_source_autoload()
source sautest/autoload/sourced.vim
call assert_equal(1, g:loaded_sourced_vim)
endfunc
func Test_autoload_vim9script()
call assert_equal('some', auto9#getsome())
call assert_equal(49, auto9#add42(7))
endfunc

View File

@ -1355,7 +1355,7 @@ func Test_input_func()
func! Tcomplete(arglead, cmdline, pos)
return "item1\nitem2\nitem3"
endfunc
call feedkeys(":let c = input('Q? ', '' , 'custom,Tcomplete')\<CR>"
call feedkeys(":let c = input('Q? ', '', 'custom,Tcomplete')\<CR>"
\ .. "\<C-A>\<CR>", 'xt')
delfunc Tcomplete
call assert_equal('item1 item2 item3', c)

View File

@ -120,7 +120,7 @@ func Test_gn_command()
sil! %d_
" search using the \zs atom
call setline(1, [' nnoremap', '' , 'nnoremap'])
call setline(1, [' nnoremap', '', 'nnoremap'])
set wrapscan&vim
let @/ = '\_s\zsnnoremap'
$

View File

@ -23,6 +23,9 @@ func Test_list_create()
call assert_equal(10, x)
endfunc
" This was allowed in legacy Vim script
let s:list_with_spaces = [1 , 2 , 3]
" List slices
func Test_list_slice()
let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},]
@ -202,6 +205,10 @@ func Test_dict()
call assert_fails("let d={'k' : i}", 'E121:')
endfunc
" This was allowed in legacy Vim script
let s:dict_with_spaces = {'one' : 1 , 'two' : 2 , 'three' : 3}
let s:dict_with_spaces_lit = #{one : 1 , two : 2 , three : 3}
" Dictionary identity
func Test_dict_identity()
let d = {001: 'asd', 'b': [1, 2, function('strlen')], -1: {'a': 1},}

View File

@ -588,7 +588,7 @@ func Test_popup_drag_termwin()
call setline(1, range(100))
for nr in range(7)
call setline(nr * 12 + 1, "fold {{{")
call setline(nr * 12 + 11 , "end }}}")
call setline(nr * 12 + 11, "end }}}")
endfor
%foldclose
set shell=/bin/sh noruler

View File

@ -350,7 +350,7 @@ function Test_tabpage_with_tabprevious()
call Check_tab_count(6, cmd . ' 3', 3)
call Check_tab_count(6, cmd . ' 8', 4)
for n in range(2)
for c in ['0', '.+3', '+', '+2' , '-', '-2' , '$', '+99', '-99']
for c in ['0', '.+3', '+', '+2', '-', '-2', '$', '+99', '-99']
if n == 0 " pre count
let entire_cmd = c . cmd
let err_code = 'E16:'

View File

@ -201,28 +201,28 @@ func Test_match()
call assert_equal("c", matchstr("abcd", ".", 2, 0))
call assert_equal("a", matchstr("abcd", ".", 0, -1))
call assert_equal(-1, match("abcd", ".", 0, 5))
call assert_equal(0 , match("abcd", ".", 0, -1))
call assert_equal(0 , match('abc', '.', 0, 1))
call assert_equal(1 , match('abc', '.', 0, 2))
call assert_equal(2 , match('abc', '.', 0, 3))
call assert_equal(0, match("abcd", ".", 0, -1))
call assert_equal(0, match('abc', '.', 0, 1))
call assert_equal(1, match('abc', '.', 0, 2))
call assert_equal(2, match('abc', '.', 0, 3))
call assert_equal(-1, match('abc', '.', 0, 4))
call assert_equal(1 , match('abc', '.', 1, 1))
call assert_equal(2 , match('abc', '.', 2, 1))
call assert_equal(1, match('abc', '.', 1, 1))
call assert_equal(2, match('abc', '.', 2, 1))
call assert_equal(-1, match('abc', '.', 3, 1))
call assert_equal(3 , match('abc', '$', 0, 1))
call assert_equal(3, match('abc', '$', 0, 1))
call assert_equal(-1, match('abc', '$', 0, 2))
call assert_equal(3 , match('abc', '$', 1, 1))
call assert_equal(3 , match('abc', '$', 2, 1))
call assert_equal(3 , match('abc', '$', 3, 1))
call assert_equal(3, match('abc', '$', 1, 1))
call assert_equal(3, match('abc', '$', 2, 1))
call assert_equal(3, match('abc', '$', 3, 1))
call assert_equal(-1, match('abc', '$', 4, 1))
call assert_equal(0 , match('abc', '\zs', 0, 1))
call assert_equal(1 , match('abc', '\zs', 0, 2))
call assert_equal(2 , match('abc', '\zs', 0, 3))
call assert_equal(3 , match('abc', '\zs', 0, 4))
call assert_equal(0, match('abc', '\zs', 0, 1))
call assert_equal(1, match('abc', '\zs', 0, 2))
call assert_equal(2, match('abc', '\zs', 0, 3))
call assert_equal(3, match('abc', '\zs', 0, 4))
call assert_equal(-1, match('abc', '\zs', 0, 5))
call assert_equal(1 , match('abc', '\zs', 1, 1))
call assert_equal(2 , match('abc', '\zs', 2, 1))
call assert_equal(3 , match('abc', '\zs', 3, 1))
call assert_equal(1, match('abc', '\zs', 1, 1))
call assert_equal(2, match('abc', '\zs', 2, 1))
call assert_equal(3, match('abc', '\zs', 3, 1))
call assert_equal(-1, match('abc', '\zs', 4, 1))
endfunc

View File

@ -1219,7 +1219,7 @@ func Test_prop_func_invalid_args()
call assert_fails('call prop_find({}, "x")', 'E474:')
call assert_fails('call prop_find({"lnum" : -2})', 'E16:')
call assert_fails('call prop_list(1, [])', 'E715:')
call assert_fails('call prop_list(-1 , {})', 'E16:')
call assert_fails('call prop_list(-1, {})', 'E16:')
call assert_fails('call prop_remove([])', 'E474:')
call assert_fails('call prop_remove({}, -2)', 'E16:')
call assert_fails('call prop_remove({})', 'E968:')

View File

@ -699,6 +699,24 @@ def Test_disassemble_lambda()
instr)
enddef
def NestedOuter()
def g:Inner()
echomsg "inner"
enddef
enddef
def Test_nested_func()
let instr = execute('disassemble NestedOuter')
assert_match('NestedOuter\_s*' ..
'def g:Inner()\_s*' ..
'echomsg "inner"\_s*' ..
'enddef\_s*' ..
'\d NEWFUNC <lambda>\d\+ Inner\_s*' ..
'\d PUSHNR 0\_s*' ..
'\d RETURN',
instr)
enddef
def AndOr(arg: any): string
if arg == 1 && arg != 2 || arg == 4
return 'yes'

View File

@ -831,7 +831,7 @@ def Test_expr5()
enddef
def Test_expr5_vim9script()
# only checks line continuation
# check line continuation
let lines =<< trim END
vim9script
let var = 11
@ -848,6 +848,30 @@ def Test_expr5_vim9script()
assert_equal('onetwo', var)
END
CheckScriptSuccess(lines)
lines =<< trim END
vim9script
echo 'abc' is# 'abc'
END
CheckScriptFailure(lines, 'E15:')
lines =<< trim END
vim9script
echo 'abc' is? 'abc'
END
CheckScriptFailure(lines, 'E15:')
lines =<< trim END
vim9script
echo 'abc' isnot# 'abc'
END
CheckScriptFailure(lines, 'E15:')
lines =<< trim END
vim9script
echo 'abc' isnot? 'abc'
END
CheckScriptFailure(lines, 'E15:')
enddef
def Test_expr5_float()
@ -1638,8 +1662,9 @@ enddef
def Test_expr7_method_call()
new
setline(1, ['first', 'last'])
eval 'second'->append(1)
assert_equal(['first', 'second', 'last'], getline(1, '$'))
'second'->append(1)
"third"->append(2)
assert_equal(['first', 'second', 'third', 'last'], getline(1, '$'))
bwipe!
let bufnr = bufnr()

View File

@ -133,6 +133,28 @@ def Test_nested_function()
CheckDefFailure(['func Nested()', 'endfunc'], 'E1086:')
enddef
def Test_nested_global_function()
let lines =<< trim END
vim9script
def Outer()
def g:Inner(): string
return 'inner'
enddef
enddef
disass Outer
Outer()
assert_equal('inner', g:Inner())
delfunc g:Inner
Outer()
assert_equal('inner', g:Inner())
delfunc g:Inner
Outer()
assert_equal('inner', g:Inner())
delfunc g:Inner
END
CheckScriptSuccess(lines)
enddef
func Test_call_default_args_from_func()
call assert_equal('string', MyDefaultArgs())
call assert_equal('one', MyDefaultArgs('one'))
@ -502,8 +524,11 @@ def Test_vim9script_call()
assert_equal('some', var)
# line starting with single quote is not a mark
# line starting with double quote can be a method call
'asdfasdf'->MyFunc()
assert_equal('asdfasdf', var)
"xyz"->MyFunc()
assert_equal('xyz', var)
def UseString()
'xyork'->MyFunc()
@ -511,6 +536,12 @@ def Test_vim9script_call()
UseString()
assert_equal('xyork', var)
def UseString2()
"knife"->MyFunc()
enddef
UseString2()
assert_equal('knife', var)
# prepending a colon makes it a mark
new
setline(1, ['aaa', 'bbb', 'ccc'])

View File

@ -109,6 +109,9 @@ def Test_assignment()
call CheckDefFailure(['&ts = 3', 'let asdf'], 'E1022:')
&ts = 8
call CheckDefFailure(['let s:var = 123'], 'E1101:')
call CheckDefFailure(['let s:var: number'], 'E1101:')
g:inc_counter += 1
assert_equal(2, g:inc_counter)
@ -136,6 +139,28 @@ def Test_assignment()
assert_equal('noneagain', v:errmsg)
call CheckDefFailure(['v:errmsg += "more"'], 'E1013:')
call CheckDefFailure(['v:errmsg += 123'], 'E1013:')
# single letter variables
a = 123
assert_equal(123, a)
let b: number
b = 123
assert_equal(123, b)
let g: number
g = 123
assert_equal(123, g)
let s: number
s = 123
assert_equal(123, s)
let t: number
t = 123
assert_equal(123, t)
let v: number
v = 123
assert_equal(123, v)
let w: number
w = 123
assert_equal(123, w)
enddef
def Test_vim9_single_char_vars()
@ -333,6 +358,25 @@ def Test_assignment_var_list()
assert_equal(['three'], vrem)
enddef
def Test_assignment_vim9script()
let lines =<< trim END
vim9script
def Func(): list<number>
return [1, 2]
enddef
let var1: number
let var2: number
[var1, var2] =
Func()
assert_equal(1, var1)
assert_equal(2, var2)
let ll =
Func()
assert_equal([1, 2], ll)
END
CheckScriptSuccess(lines)
enddef
def Mess(): string
v:foldstart = 123
return 'xxx'

View File

@ -366,7 +366,7 @@ register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state)
if (fp == NULL)
return NULL;
fp->uf_dfunc_idx = UF_NOT_COMPILED;
fp->uf_def_status = UF_NOT_COMPILED;
fp->uf_refcount = 1;
fp->uf_varargs = TRUE;
fp->uf_flags = FC_CFUNC;
@ -642,6 +642,10 @@ get_func_tv(
break;
}
++argcount;
// The comma should come right after the argument, but this wasn't
// checked previously, thus only enforce it in Vim9 script.
if (!in_vim9script())
argp = skipwhite(argp);
if (*argp != ',')
break;
}
@ -1065,7 +1069,8 @@ func_remove(ufunc_T *fp)
{
// When there is a def-function index do not actually remove the
// function, so we can find the index when defining the function again.
if (fp->uf_def_status == UF_COMPILED)
// Do remove it when it's a copy.
if (fp->uf_def_status == UF_COMPILED && (fp->uf_flags & FC_COPY) == 0)
fp->uf_flags |= FC_DEAD;
else
hash_remove(&func_hashtab, hi);
@ -1118,7 +1123,8 @@ func_clear(ufunc_T *fp, int force)
// clear this function
func_clear_items(fp);
funccal_unref(fp->uf_scoped, fp, force);
clear_def_function(fp);
if ((fp->uf_flags & FC_COPY) == 0)
clear_def_function(fp);
}
/*
@ -1146,12 +1152,83 @@ func_free(ufunc_T *fp, int force)
func_clear_free(ufunc_T *fp, int force)
{
func_clear(fp, force);
if (force || fp->uf_dfunc_idx == 0)
if (force || fp->uf_dfunc_idx == 0 || (fp->uf_flags & FC_COPY))
func_free(fp, force);
else
fp->uf_flags |= FC_DEAD;
}
/*
* Copy already defined function "lambda" to a new function with name "global".
* This is for when a compiled function defines a global function.
*/
void
copy_func(char_u *lambda, char_u *global)
{
ufunc_T *ufunc = find_func_even_dead(lambda, TRUE, NULL);
ufunc_T *fp;
if (ufunc == NULL)
semsg(_("E1102: lambda function not found: %s"), lambda);
else
{
// TODO: handle ! to overwrite
fp = find_func(global, TRUE, NULL);
if (fp != NULL)
{
semsg(_(e_funcexts), global);
return;
}
fp = alloc_clear(offsetof(ufunc_T, uf_name) + STRLEN(global) + 1);
if (fp == NULL)
return;
fp->uf_varargs = ufunc->uf_varargs;
fp->uf_flags = (ufunc->uf_flags & ~FC_VIM9) | FC_COPY;
fp->uf_def_status = ufunc->uf_def_status;
fp->uf_dfunc_idx = ufunc->uf_dfunc_idx;
if (ga_copy_strings(&fp->uf_args, &ufunc->uf_args) == FAIL
|| ga_copy_strings(&fp->uf_def_args, &ufunc->uf_def_args)
== FAIL
|| ga_copy_strings(&fp->uf_lines, &ufunc->uf_lines) == FAIL)
goto failed;
fp->uf_name_exp = ufunc->uf_name_exp == NULL ? NULL
: vim_strsave(ufunc->uf_name_exp);
if (ufunc->uf_arg_types != NULL)
{
fp->uf_arg_types = ALLOC_MULT(type_T *, fp->uf_args.ga_len);
if (fp->uf_arg_types == NULL)
goto failed;
mch_memmove(fp->uf_arg_types, ufunc->uf_arg_types,
sizeof(type_T *) * fp->uf_args.ga_len);
}
if (ufunc->uf_def_arg_idx != NULL)
{
fp->uf_def_arg_idx = ALLOC_MULT(int, fp->uf_def_args.ga_len + 1);
if (fp->uf_def_arg_idx == NULL)
goto failed;
mch_memmove(fp->uf_def_arg_idx, ufunc->uf_def_arg_idx,
sizeof(int) * fp->uf_def_args.ga_len + 1);
}
if (ufunc->uf_va_name != NULL)
{
fp->uf_va_name = vim_strsave(ufunc->uf_va_name);
if (fp->uf_va_name == NULL)
goto failed;
}
fp->uf_refcount = 1;
STRCPY(fp->uf_name, global);
hash_add(&func_hashtab, UF2HIKEY(fp));
}
return;
failed:
func_clear_free(fp, TRUE);
}
/*
* Call a user function.
@ -2517,6 +2594,8 @@ list_functions(regmatch_T *regmatch)
/*
* ":function" also supporting nested ":def".
* When "name_arg" is not NULL this is a nested function, using "name_arg" for
* the function name.
* Returns a pointer to the function or NULL if no function defined.
*/
ufunc_T *

View File

@ -754,6 +754,28 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1330,
/**/
1329,
/**/
1328,
/**/
1327,
/**/
1326,
/**/
1325,
/**/
1324,
/**/
1323,
/**/
1322,
/**/
1321,
/**/
1320,
/**/
1319,
/**/

View File

@ -79,6 +79,7 @@ typedef enum {
ISN_PCALL_END, // cleanup after ISN_PCALL with cpf_top set
ISN_RETURN, // return, result is on top of stack
ISN_FUNCREF, // push a function ref to dfunc isn_arg.funcref
ISN_NEWFUNC, // create a global function from a lambda function
// expression operations
ISN_JUMP, // jump if condition is matched isn_arg.jump
@ -237,6 +238,12 @@ typedef struct {
int fr_var_idx; // variable to store partial
} funcref_T;
// arguments to ISN_NEWFUNC
typedef struct {
char_u *nf_lambda; // name of the lambda already defined
char_u *nf_global; // name of the global function to be created
} newfunc_T;
// arguments to ISN_CHECKLEN
typedef struct {
int cl_min_len; // minimum length
@ -281,6 +288,7 @@ struct isn_S {
script_T script;
unlet_T unlet;
funcref_T funcref;
newfunc_T newfunc;
checklen_T checklen;
shuffle_T shuffle;
} isn_arg;

View File

@ -1522,6 +1522,27 @@ generate_FUNCREF(cctx_T *cctx, int dfunc_idx)
return OK;
}
/*
* Generate an ISN_NEWFUNC instruction.
*/
static int
generate_NEWFUNC(cctx_T *cctx, char_u *lambda_name, char_u *func_name)
{
isn_T *isn;
char_u *name;
RETURN_OK_IF_SKIP(cctx);
name = vim_strsave(lambda_name);
if (name == NULL)
return FAIL;
if ((isn = generate_instr(cctx, ISN_NEWFUNC)) == NULL)
return FAIL;
isn->isn_arg.newfunc.nf_lambda = name;
isn->isn_arg.newfunc.nf_global = func_name;
return OK;
}
/*
* Generate an ISN_JUMP instruction.
*/
@ -3633,7 +3654,7 @@ get_vim_constant(char_u **arg, typval_T *rettv)
}
}
static exptype_T
exptype_T
get_compare_type(char_u *p, int *len, int *type_is)
{
exptype_T type = EXPR_UNKNOWN;
@ -4875,11 +4896,13 @@ exarg_getline(
static char_u *
compile_nested_function(exarg_T *eap, cctx_T *cctx)
{
int is_global = *eap->arg == 'g' && eap->arg[1] == ':';
char_u *name_start = eap->arg;
char_u *name_end = to_name_end(eap->arg, FALSE);
char_u *name_end = to_name_end(eap->arg, is_global);
char_u *name = get_lambda_name();
lvar_T *lvar;
ufunc_T *ufunc;
int r;
eap->arg = name_end;
eap->getline = exarg_getline;
@ -4894,16 +4917,28 @@ compile_nested_function(exarg_T *eap, cctx_T *cctx)
&& compile_def_function(ufunc, TRUE, cctx) == FAIL)
return NULL;
// Define a local variable for the function reference.
lvar = reserve_local(cctx, name_start, name_end - name_start,
TRUE, ufunc->uf_func_type);
if (is_global)
{
char_u *func_name = vim_strnsave(name_start + 2,
name_end - name_start - 2);
if (generate_FUNCREF(cctx, ufunc->uf_dfunc_idx) == FAIL
|| generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL) == FAIL)
return NULL;
if (func_name == NULL)
r = FAIL;
else
r = generate_NEWFUNC(cctx, name, func_name);
}
else
{
// Define a local variable for the function reference.
lvar = reserve_local(cctx, name_start, name_end - name_start,
TRUE, ufunc->uf_func_type);
if (generate_FUNCREF(cctx, ufunc->uf_dfunc_idx) == FAIL)
return NULL;
r = generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL);
}
// TODO: warning for trailing text?
return (char_u *)"";
return r == FAIL ? NULL : (char_u *)"";
}
/*
@ -5237,7 +5272,7 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
goto theend;
}
}
else if (STRNCMP(var_start, "g:", 2) == 0)
else if (varlen > 1 && STRNCMP(var_start, "g:", 2) == 0)
{
dest = dest_global;
if (is_decl)
@ -5246,7 +5281,7 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
goto theend;
}
}
else if (STRNCMP(var_start, "b:", 2) == 0)
else if (varlen > 1 && STRNCMP(var_start, "b:", 2) == 0)
{
dest = dest_buffer;
if (is_decl)
@ -5255,7 +5290,7 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
goto theend;
}
}
else if (STRNCMP(var_start, "w:", 2) == 0)
else if (varlen > 1 && STRNCMP(var_start, "w:", 2) == 0)
{
dest = dest_window;
if (is_decl)
@ -5264,7 +5299,7 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
goto theend;
}
}
else if (STRNCMP(var_start, "t:", 2) == 0)
else if (varlen > 1 && STRNCMP(var_start, "t:", 2) == 0)
{
dest = dest_tab;
if (is_decl)
@ -5273,7 +5308,7 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
goto theend;
}
}
else if (STRNCMP(var_start, "v:", 2) == 0)
else if (varlen > 1 && STRNCMP(var_start, "v:", 2) == 0)
{
typval_T *vtv;
int di_flags;
@ -5337,14 +5372,18 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
goto theend;
}
}
else if (STRNCMP(var_start, "s:", 2) == 0
else if ((varlen > 1 && STRNCMP(var_start, "s:", 2) == 0)
|| lookup_script(var_start, varlen) == OK
|| find_imported(var_start, varlen, cctx) != NULL)
{
dest = dest_script;
if (is_decl)
{
semsg(_("E1054: Variable already declared in the script: %s"),
if ((varlen > 1 && STRNCMP(var_start, "s:", 2) == 0))
semsg(_("E1101: Cannot declare a script variable in a function: %s"),
name);
else
semsg(_("E1054: Variable already declared in the script: %s"),
name);
goto theend;
}
@ -7637,6 +7676,11 @@ delete_instr(isn_T *isn)
}
break;
case ISN_NEWFUNC:
vim_free(isn->isn_arg.newfunc.nf_lambda);
vim_free(isn->isn_arg.newfunc.nf_global);
break;
case ISN_2BOOL:
case ISN_2STRING:
case ISN_ADDBLOB:

View File

@ -723,7 +723,10 @@ call_def_function(
dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
+ ufunc->uf_dfunc_idx;
if (dfunc->df_instr == NULL)
{
iemsg("using call_def_function() on not compiled function");
return FAIL;
}
}
CLEAR_FIELD(ectx);
@ -1726,6 +1729,15 @@ call_def_function(
}
break;
// Create a global function from a lambda.
case ISN_NEWFUNC:
{
newfunc_T *newfunc = &iptr->isn_arg.newfunc;
copy_func(newfunc->nf_lambda, newfunc->nf_global);
}
break;
// jump if a condition is met
case ISN_JUMP:
{
@ -2912,6 +2924,15 @@ ex_disassemble(exarg_T *eap)
}
break;
case ISN_NEWFUNC:
{
newfunc_T *newfunc = &iptr->isn_arg.newfunc;
smsg("%4d NEWFUNC %s %s", current,
newfunc->nf_lambda, newfunc->nf_global);
}
break;
case ISN_JUMP:
{
char *when = "?";