# HG changeset patch # User Bram Moolenaar # Date 1581008404 -3600 # Node ID e99e6d7945978dd033f5fa2e9d0ca14209d67461 # Parent 6bbb4ad91d21c63867fa343ab07e120c75a054dc patch 8.2.0222: Vim9: optional function arguments don't work yet Commit: https://github.com/vim/vim/commit/170fcfcf250954d76fca86e3fed088ddfdb49383 Author: Bram Moolenaar Date: Thu Feb 6 17:51:35 2020 +0100 patch 8.2.0222: Vim9: optional function arguments don't work yet Problem: Vim9: optional function arguments don't work yet. Solution: Implement optional function arguments. diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1515,6 +1515,8 @@ typedef struct type_T **uf_arg_types; // argument types (count == uf_args.ga_len) type_T *uf_ret_type; // return type garray_T uf_type_list; // types used in arg and return types + int *uf_def_arg_idx; // instruction indexes for evaluating + // uf_def_args; length: uf_def_args.ga_len + 1 char_u *uf_va_name; // name from "...name" or NULL type_T *uf_va_type; // type from "...name: type" or NULL diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -139,6 +139,39 @@ def Test_call_varargs() assert_equal('one,two,three', MyVarargs('one', 'two', 'three')) enddef +def MyDefaultArgs(name = 'string'): string + return name +enddef + +def Test_call_default_args() + assert_equal('string', MyDefaultArgs()) + assert_equal('one', MyDefaultArgs('one')) + assert_fails('call MyDefaultArgs("one", "two")', 'E118:') +enddef + +func Test_call_default_args_from_func() + call assert_equal('string', MyDefaultArgs()) + call assert_equal('one', MyDefaultArgs('one')) + call assert_fails('call MyDefaultArgs("one", "two")', 'E118:') +endfunc + +" Default arg and varargs +def MyDefVarargs(one: string, two = 'foo', ...rest: list): string + let res = one .. ',' .. two + for s in rest + res ..= ',' .. s + endfor + return res +enddef + +def Test_call_def_varargs() + call assert_fails('call MyDefVarargs()', 'E119:') + assert_equal('one,foo', MyDefVarargs('one')) + assert_equal('one,two', MyDefVarargs('one', 'two')) + assert_equal('one,two,three', MyDefVarargs('one', 'two', 'three')) +enddef + + "def Test_call_func_defined_later() " call assert_equal('one', DefineLater('one')) " call assert_fails('call NotDefined("one")', 'E99:') @@ -148,25 +181,6 @@ func DefineLater(arg) return a:arg endfunc -def MyDefaultArgs(name = 'string'): string - return name -enddef - -func Test_call_default_args_from_func() - " TODO: implement using default value for optional argument - "call assert_equal('string', MyDefaultArgs()) - call assert_fails('call MyDefaultArgs()', 'optional arguments not implemented yet') - call assert_equal('one', MyDefaultArgs('one')) - call assert_fails('call MyDefaultArgs("one", "two")', 'E118:') -endfunc - -def Test_call_default_args() - " TODO: implement using default value for optional argument - "assert_equal('string', MyDefaultArgs()) - assert_equal('one', MyDefaultArgs('one')) - assert_fails('call MyDefaultArgs("one", "two")', 'E118:') -enddef - def Test_return_type_wrong() CheckScriptFailure(['def Func(): number', 'return "a"', 'enddef'], 'expected number but got string') CheckScriptFailure(['def Func(): string', 'return 1', 'enddef'], 'expected string but got number') diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -200,6 +200,7 @@ get_function_args( { typval_T rettv; + // find the end of the expression (doesn't evaluate it) any_default = TRUE; p = skipwhite(p) + 1; p = skipwhite(p); diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -743,6 +743,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 222, +/**/ 221, /**/ 220, diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -956,11 +956,12 @@ generate_BCALL(cctx_T *cctx, int func_id * Return FAIL if the number of arguments is wrong. */ static int -generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int argcount) +generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount) { isn_T *isn; garray_T *stack = &cctx->ctx_type_stack; int regular_args = ufunc->uf_args.ga_len; + int argcount = pushed_argcount; if (argcount > regular_args && !has_varargs(ufunc)) { @@ -978,9 +979,13 @@ generate_CALL(cctx_T *cctx, ufunc_T *ufu { int count = argcount - regular_args; - // TODO: add default values for optional arguments? - generate_NEWLIST(cctx, count < 0 ? 0 : count); - argcount = regular_args + 1; + // If count is negative an empty list will be added after evaluating + // default values for missing optional arguments. + if (count >= 0) + { + generate_NEWLIST(cctx, count); + argcount = regular_args + 1; + } } if ((isn = generate_instr(cctx, @@ -4600,6 +4605,44 @@ compile_def_function(ufunc_T *ufunc, int // Most modern script version. current_sctx.sc_version = SCRIPT_VERSION_VIM9; + if (ufunc->uf_def_args.ga_len > 0) + { + int count = ufunc->uf_def_args.ga_len; + int i; + char_u *arg; + int off = STACK_FRAME_SIZE + (ufunc->uf_va_name != NULL ? 1 : 0); + + // Produce instructions for the default values of optional arguments. + // Store the instruction index in uf_def_arg_idx[] so that we know + // where to start when the function is called, depending on the number + // of arguments. + ufunc->uf_def_arg_idx = ALLOC_CLEAR_MULT(int, count + 1); + if (ufunc->uf_def_arg_idx == NULL) + goto erret; + for (i = 0; i < count; ++i) + { + ufunc->uf_def_arg_idx[i] = instr->ga_len; + arg = ((char_u **)(ufunc->uf_def_args.ga_data))[i]; + if (compile_expr1(&arg, &cctx) == FAIL + || generate_STORE(&cctx, ISN_STORE, + i - count - off, NULL) == FAIL) + goto erret; + } + + // If a varargs is following, push an empty list. + if (ufunc->uf_va_name != NULL) + { + if (generate_NEWLIST(&cctx, 0) == FAIL + || generate_STORE(&cctx, ISN_STORE, -off, NULL) == FAIL) + goto erret; + } + + ufunc->uf_def_arg_idx[count] = instr->ga_len; + } + + /* + * Loop over all the lines of the function and generate instructions. + */ for (;;) { if (line != NULL && *line == '|') diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -70,7 +70,7 @@ typedef struct { #define STACK_TV_BOT(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_stack.ga_len + idx) /* - * Return the number of arguments, including any vararg. + * Return the number of arguments, including optional arguments and any vararg. */ static int ufunc_argcount(ufunc_T *ufunc) @@ -79,6 +79,35 @@ ufunc_argcount(ufunc_T *ufunc) } /* + * Set the instruction index, depending on omitted arguments, where the default + * values are to be computed. If all optional arguments are present, start + * with the function body. + * The expression evaluation is at the start of the instructions: + * 0 -> EVAL default1 + * STORE arg[-2] + * 1 -> EVAL default2 + * STORE arg[-1] + * 2 -> function body + */ + static void +init_instr_idx(ufunc_T *ufunc, int argcount, ectx_T *ectx) +{ + if (ufunc->uf_def_args.ga_len == 0) + ectx->ec_iidx = 0; + else + { + int defcount = ufunc->uf_args.ga_len - argcount; + + // If there is a varargs argument defcount can be negative, no defaults + // to evaluate then. + if (defcount < 0) + defcount = 0; + ectx->ec_iidx = ufunc->uf_def_arg_idx[ + ufunc->uf_def_args.ga_len - defcount]; + } +} + +/* * Call compiled function "cdf_idx" from compiled code. * * Stack has: @@ -107,23 +136,15 @@ call_dfunc(int cdf_idx, int argcount, ec if (ga_grow(&ectx->ec_stack, optcount + 3 + dfunc->df_varcount) == FAIL) return FAIL; -// TODO: Put omitted argument default values on the stack. - if (optcount > 0) - { - emsg("optional arguments not implemented yet"); - return FAIL; - } if (optcount < 0) { emsg("argument count wrong?"); return FAIL; } -// for (idx = argcount - dfunc->df_minarg; -// idx < dfunc->df_maxarg; ++idx) -// { -// copy_tv(&dfunc->df_defarg[idx], STACK_TV_BOT(0)); -// ++ectx->ec_stack.ga_len; -// } + + // Reserve space for omitted optional arguments, filled in soon. + // Also any empty varargs argument. + ectx->ec_stack.ga_len += optcount; // Store current execution state in stack frame for ISN_RETURN. // TODO: If the actual number of arguments doesn't match what the called @@ -142,7 +163,9 @@ call_dfunc(int cdf_idx, int argcount, ec ectx->ec_dfunc_idx = cdf_idx; ectx->ec_instr = dfunc->df_instr; estack_push_ufunc(ETYPE_UFUNC, dfunc->df_ufunc, 1); - ectx->ec_iidx = 0; + + // Decide where to start execution, handles optional arguments. + init_instr_idx(ufunc, argcount, ectx); return OK; } @@ -156,9 +179,9 @@ call_dfunc(int cdf_idx, int argcount, ec static void func_return(ectx_T *ectx) { - int ret_idx = ectx->ec_stack.ga_len - 1; int idx; dfunc_T *dfunc; + int top; // execution context goes one level up estack_pop(); @@ -166,17 +189,27 @@ func_return(ectx_T *ectx) // Clear the local variables and temporary values, but not // the return value. for (idx = ectx->ec_frame + STACK_FRAME_SIZE; - idx < ectx->ec_stack.ga_len - 1; ++idx) + idx < ectx->ec_stack.ga_len - 1; ++idx) clear_tv(STACK_TV(idx)); + + // Clear the arguments. dfunc = ((dfunc_T *)def_functions.ga_data) + ectx->ec_dfunc_idx; - ectx->ec_stack.ga_len = ectx->ec_frame - - ufunc_argcount(dfunc->df_ufunc) + 1; + top = ectx->ec_frame - ufunc_argcount(dfunc->df_ufunc); + for (idx = top; idx < ectx->ec_frame; ++idx) + clear_tv(STACK_TV(idx)); + + // Restore the previous frame. ectx->ec_dfunc_idx = STACK_TV(ectx->ec_frame)->vval.v_number; ectx->ec_iidx = STACK_TV(ectx->ec_frame + 1)->vval.v_number; ectx->ec_frame = STACK_TV(ectx->ec_frame + 2)->vval.v_number; - *STACK_TV_BOT(-1) = *STACK_TV(ret_idx); dfunc = ((dfunc_T *)def_functions.ga_data) + ectx->ec_dfunc_idx; ectx->ec_instr = dfunc->df_instr; + + // Reset the stack to the position before the call, move the return value + // to the top of the stack. + idx = ectx->ec_stack.ga_len - 1; + ectx->ec_stack.ga_len = top + 1; + *STACK_TV_BOT(-1) = *STACK_TV(idx); } #undef STACK_TV @@ -362,7 +395,7 @@ call_def_function( int idx; int ret = FAIL; dfunc_T *dfunc; - int optcount = ufunc_argcount(ufunc) - argc; + int defcount = ufunc->uf_args.ga_len - argc; // Get pointer to item in the stack. #define STACK_TV(idx) (((typval_T *)ectx.ec_stack.ga_data) + idx) @@ -388,17 +421,18 @@ call_def_function( copy_tv(&argv[idx], STACK_TV_BOT(0)); ++ectx.ec_stack.ga_len; } + // Make space for omitted arguments, will store default value below. + if (defcount > 0) + for (idx = 0; idx < defcount; ++idx) + { + STACK_TV_BOT(0)->v_type = VAR_UNKNOWN; + ++ectx.ec_stack.ga_len; + } // Frame pointer points to just after arguments. ectx.ec_frame = ectx.ec_stack.ga_len; initial_frame_ptr = ectx.ec_frame; -// TODO: Put omitted argument default values on the stack. - if (optcount > 0) - { - emsg("optional arguments not implemented yet"); - return FAIL; - } // dummy frame entries for (idx = 0; idx < STACK_FRAME_SIZE; ++idx) { @@ -413,7 +447,10 @@ call_def_function( ectx.ec_stack.ga_len += dfunc->df_varcount; ectx.ec_instr = dfunc->df_instr; - ectx.ec_iidx = 0; + + // Decide where to start execution, handles optional arguments. + init_instr_idx(ufunc, argc, &ectx); + for (;;) { isn_T *iptr; @@ -1657,7 +1694,11 @@ ex_disassemble(exarg_T *eap) break; case ISN_STORE: - smsg("%4d STORE $%lld", current, iptr->isn_arg.number); + if (iptr->isn_arg.number < 0) + smsg("%4d STORE arg[%lld]", current, + iptr->isn_arg.number + STACK_FRAME_SIZE); + else + smsg("%4d STORE $%lld", current, iptr->isn_arg.number); break; case ISN_STOREV: smsg("%4d STOREV v:%s", current,