To: vim_dev@googlegroups.com Subject: Patch 8.2.0695 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.0695 Problem: Vim9: cannot define a function inside a function. Solution: Initial support for :def inside :def. Files: src/userfunc.c, src/proto/userfunc.pro, src/vim9compile.c, src/vim9execute.c, src/testdir/test_vim9_func.vim *** ../vim-8.2.0694/src/userfunc.c 2020-05-03 15:38:12.983700666 +0200 --- src/userfunc.c 2020-05-04 23:13:42.067556908 +0200 *************** *** 329,334 **** --- 329,347 ---- } /* + * Get a name for a lambda. Returned in static memory. + */ + char_u * + get_lambda_name(void) + { + static char_u name[30]; + static int lambda_no = 0; + + sprintf((char*)name, "%d", ++lambda_no); + return name; + } + + /* * Parse a lambda expression and get a Funcref from "*arg". * Return OK or FAIL. Returns NOTDONE for dict or {expr}. */ *************** *** 344,350 **** int ret; char_u *start = skipwhite(*arg + 1); char_u *s, *e; - static int lambda_no = 0; int *old_eval_lavars = eval_lavars_used; int eval_lavars = FALSE; --- 357,362 ---- *************** *** 392,400 **** { int len, flags = 0; char_u *p; ! char_u name[20]; ! ! sprintf((char*)name, "%d", ++lambda_no); fp = alloc_clear(offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); if (fp == NULL) --- 404,410 ---- { int len, flags = 0; char_u *p; ! char_u *name = get_lambda_name(); fp = alloc_clear(offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); if (fp == NULL) *************** *** 2364,2373 **** } /* ! * ":function" */ ! void ! ex_function(exarg_T *eap) { char_u *theline; char_u *line_to_free = NULL; --- 2374,2384 ---- } /* ! * ":function" also supporting nested ":def". ! * Returns a pointer to the function or NULL if no function defined. */ ! ufunc_T * ! def_function(exarg_T *eap, char_u *name_arg, void *context) { char_u *theline; char_u *line_to_free = NULL; *************** *** 2375,2381 **** int c; int saved_did_emsg; int saved_wait_return = need_wait_return; ! char_u *name = NULL; int is_global = FALSE; char_u *p; char_u *arg; --- 2386,2392 ---- int c; int saved_did_emsg; int saved_wait_return = need_wait_return; ! char_u *name = name_arg; int is_global = FALSE; char_u *p; char_u *arg; *************** *** 2387,2393 **** int varargs = FALSE; int flags = 0; char_u *ret_type = NULL; ! ufunc_T *fp; int overwrite = FALSE; int indent; int nesting; --- 2398,2404 ---- int varargs = FALSE; int flags = 0; char_u *ret_type = NULL; ! ufunc_T *fp = NULL; int overwrite = FALSE; int indent; int nesting; *************** *** 2429,2435 **** } } eap->nextcmd = check_nextcmd(eap->arg); ! return; } /* --- 2440,2446 ---- } } eap->nextcmd = check_nextcmd(eap->arg); ! return NULL; } /* *************** *** 2469,2475 **** if (*p == '/') ++p; eap->nextcmd = check_nextcmd(p); ! return; } ga_init(&newargs); --- 2480,2486 ---- if (*p == '/') ++p; eap->nextcmd = check_nextcmd(p); ! return NULL; } ga_init(&newargs); *************** *** 2493,2517 **** * g:func global function name, same as "func" */ p = eap->arg; ! name = trans_function_name(&p, &is_global, eap->skip, ! TFN_NO_AUTOLOAD, &fudi, NULL); ! paren = (vim_strchr(p, '(') != NULL); ! if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { ! /* ! * Return on an invalid expression in braces, unless the expression ! * evaluation has been cancelled due to an aborting error, an ! * interrupt, or an exception. ! */ ! if (!aborting()) { ! if (!eap->skip && fudi.fd_newkey != NULL) ! semsg(_(e_dictkey), fudi.fd_newkey); ! vim_free(fudi.fd_newkey); ! return; } - else - eap->skip = TRUE; } // An error in a function call during evaluation of an expression in magic --- 2504,2537 ---- * g:func global function name, same as "func" */ p = eap->arg; ! if (name_arg != NULL) { ! // nested function, argument is (args). ! paren = TRUE; ! CLEAR_FIELD(fudi); ! } ! else ! { ! name = trans_function_name(&p, &is_global, eap->skip, ! TFN_NO_AUTOLOAD, &fudi, NULL); ! paren = (vim_strchr(p, '(') != NULL); ! if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { ! /* ! * Return on an invalid expression in braces, unless the expression ! * evaluation has been cancelled due to an aborting error, an ! * interrupt, or an exception. ! */ ! if (!aborting()) ! { ! if (!eap->skip && fudi.fd_newkey != NULL) ! semsg(_(e_dictkey), fudi.fd_newkey); ! vim_free(fudi.fd_newkey); ! return NULL; ! } ! else ! eap->skip = TRUE; } } // An error in a function call during evaluation of an expression in magic *************** *** 2596,2602 **** ga_init2(&newlines, (int)sizeof(char_u *), 3); ! if (!eap->skip) { // Check the name of the function. Unless it's a dictionary function // (that we are overwriting). --- 2616,2622 ---- ga_init2(&newlines, (int)sizeof(char_u *), 3); ! if (!eap->skip && name_arg == NULL) { // Check the name of the function. Unless it's a dictionary function // (that we are overwriting). *************** *** 3255,3261 **** // ":def Func()" needs to be compiled if (eap->cmdidx == CMD_def) ! compile_def_function(fp, FALSE, NULL); goto ret_free; --- 3275,3281 ---- // ":def Func()" needs to be compiled if (eap->cmdidx == CMD_def) ! compile_def_function(fp, FALSE, context); goto ret_free; *************** *** 3269,3278 **** vim_free(skip_until); vim_free(line_to_free); vim_free(fudi.fd_newkey); ! vim_free(name); vim_free(ret_type); did_emsg |= saved_did_emsg; need_wait_return |= saved_wait_return; } /* --- 3289,3310 ---- vim_free(skip_until); vim_free(line_to_free); vim_free(fudi.fd_newkey); ! if (name != name_arg) ! vim_free(name); vim_free(ret_type); did_emsg |= saved_did_emsg; need_wait_return |= saved_wait_return; + + return fp; + } + + /* + * ":function" + */ + void + ex_function(exarg_T *eap) + { + def_function(eap, NULL, NULL); } /* *** ../vim-8.2.0694/src/proto/userfunc.pro 2020-04-27 22:47:45.186176148 +0200 --- src/proto/userfunc.pro 2020-05-04 22:44:39.358158835 +0200 *************** *** 2,7 **** --- 2,8 ---- void func_init(void); hashtab_T *func_tbl_get(void); int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, garray_T *argtypes, int *varargs, garray_T *default_args, int skip, exarg_T *eap, char_u **line_to_free); + char_u *get_lambda_name(void); int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate); char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload); void emsg_funcname(char *ermsg, char_u *name); *************** *** 22,27 **** --- 23,29 ---- int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe); char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial); char_u *untrans_function_name(char_u *name); + ufunc_T *def_function(exarg_T *eap, char_u *name_arg, void *context); void ex_function(exarg_T *eap); int eval_fname_script(char_u *p); int translated_function_exists(char_u *name, int is_global); *** ../vim-8.2.0694/src/vim9compile.c 2020-05-03 22:30:44.038682664 +0200 --- src/vim9compile.c 2020-05-04 23:23:22.838253954 +0200 *************** *** 101,106 **** --- 101,107 ---- int lv_from_outer; // when TRUE using ctx_outer scope int lv_const; // when TRUE cannot be assigned to int lv_arg; // when TRUE this is an argument + int lv_func_idx; // for nested function } lvar_T; /* *************** *** 2615,2620 **** --- 2616,2622 ---- int error = FCERR_NONE; ufunc_T *ufunc; int res = FAIL; + lvar_T *lvar; if (varlen >= sizeof(namebuf)) { *************** *** 2641,2646 **** --- 2643,2658 ---- goto theend; } + // Check if the name is a nested function. + lvar = lookup_local(namebuf, varlen, cctx); + if (lvar != NULL && lvar->lv_func_idx > 0) + { + dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + + lvar->lv_func_idx; + res = generate_CALL(cctx, dfunc->df_ufunc, argcount); + goto theend; + } + // If we can find the function by name generate the right call. ufunc = find_func(name, FALSE, cctx); if (ufunc != NULL) *************** *** 4049,4054 **** --- 4061,4124 ---- } /* + * Get a line from the compilation context, compatible with exarg_T getline(). + * Return a pointer to the line in allocated memory. + * Return NULL for end-of-file or some error. + */ + static char_u * + exarg_getline( + int c UNUSED, + void *cookie, + int indent UNUSED, + int do_concat UNUSED) + { + cctx_T *cctx = (cctx_T *)cookie; + + if (cctx->ctx_lnum == cctx->ctx_ufunc->uf_lines.ga_len) + { + iemsg("Heredoc got to end"); + return NULL; + } + ++cctx->ctx_lnum; + return vim_strsave(((char_u **)cctx->ctx_ufunc->uf_lines.ga_data) + [cctx->ctx_lnum]); + } + + /* + * Compile a nested :def command. + */ + static char_u * + compile_nested_function(exarg_T *eap, cctx_T *cctx) + { + char_u *name_start = eap->arg; + char_u *name_end = to_name_end(eap->arg, FALSE); + char_u *name = get_lambda_name(); + lvar_T *lvar; + ufunc_T *ufunc; + + eap->arg = name_end; + eap->getline = exarg_getline; + eap->cookie = cctx; + eap->skip = cctx->ctx_skip == TRUE; + eap->forceit = FALSE; + ufunc = def_function(eap, name, cctx); + + if (ufunc == NULL) + return NULL; + + // Define a local variable for the function, but change the index to -1 to + // mark it as a function name. + lvar = reserve_local(cctx, name_start, name_end - name_start, + TRUE, &t_func_unknown); + lvar->lv_idx = 0; + ++cctx->ctx_locals_count; // doesn't count as a local variable + lvar->lv_func_idx = ufunc->uf_dfunc_idx; + + // TODO: warning for trailing? + return (char_u *)""; + } + + /* * Return the length of an assignment operator, or zero if there isn't one. */ int *************** *** 4077,4106 **** NULL }; - /* - * Get a line for "=<<". - * Return a pointer to the line in allocated memory. - * Return NULL for end-of-file or some error. - */ - static char_u * - heredoc_getline( - int c UNUSED, - void *cookie, - int indent UNUSED, - int do_concat UNUSED) - { - cctx_T *cctx = (cctx_T *)cookie; - - if (cctx->ctx_lnum == cctx->ctx_ufunc->uf_lines.ga_len) - { - iemsg("Heredoc got to end"); - return NULL; - } - ++cctx->ctx_lnum; - return vim_strsave(((char_u **)cctx->ctx_ufunc->uf_lines.ga_data) - [cctx->ctx_lnum]); - } - typedef enum { dest_local, dest_option, --- 4147,4152 ---- *************** *** 4394,4400 **** listitem_T *li; // [let] varname =<< [trim] {end} ! eap->getline = heredoc_getline; eap->cookie = cctx; l = heredoc_get(eap, op + 3, FALSE); --- 4440,4446 ---- listitem_T *li; // [let] varname =<< [trim] {end} ! eap->getline = exarg_getline; eap->cookie = cctx; l = heredoc_get(eap, op + 3, FALSE); *************** *** 6299,6307 **** switch (ea.cmdidx) { case CMD_def: case CMD_function: ! // TODO: Nested function ! emsg("Nested function not implemented yet"); goto erret; case CMD_return: --- 6345,6356 ---- switch (ea.cmdidx) { case CMD_def: + ea.arg = p; + line = compile_nested_function(&ea, &cctx); + break; + case CMD_function: ! emsg(_("E1086: Cannot use :function inside :def")); goto erret; case CMD_return: *** ../vim-8.2.0694/src/vim9execute.c 2020-05-03 15:38:12.983700666 +0200 --- src/vim9execute.c 2020-05-04 22:03:56.702103704 +0200 *************** *** 206,211 **** --- 206,216 ---- + dfunc->df_varcount + dfunc->df_closure_count) == FAIL) return FAIL; + // Closure may need the function context where it was defined. + // TODO: assuming current context. + ectx->ec_outer_stack = &ectx->ec_stack; + ectx->ec_outer_frame = ectx->ec_frame_idx; + // Move the vararg-list to below the missing optional arguments. if (vararg_count > 0 && arg_to_add > 0) *STACK_TV_BOT(arg_to_add - 1) = *STACK_TV_BOT(-1); *** ../vim-8.2.0694/src/testdir/test_vim9_func.vim 2020-05-03 22:30:44.038682664 +0200 --- src/testdir/test_vim9_func.vim 2020-05-04 23:10:34.024230320 +0200 *************** *** 2,20 **** source check.vim source view_util.vim ! ! " Check that "lines" inside ":def" results in an "error" message. ! func CheckDefFailure(lines, error) ! call writefile(['def Func()'] + a:lines + ['enddef'], 'Xdef') ! call assert_fails('so Xdef', a:error, a:lines) ! call delete('Xdef') ! endfunc ! ! func CheckScriptFailure(lines, error) ! call writefile(a:lines, 'Xdef') ! call assert_fails('so Xdef', a:error, a:lines) ! call delete('Xdef') ! endfunc func Test_def_basic() def SomeFunc(): string --- 2,8 ---- source check.vim source view_util.vim ! source vim9.vim func Test_def_basic() def SomeFunc(): string *************** *** 95,102 **** assert_equal('one', MyDefaultArgs('one')) assert_fails('call MyDefaultArgs("one", "two")', 'E118:') ! call CheckScriptFailure(['def Func(arg: number = asdf)', 'enddef'], 'E1001:') ! call CheckScriptFailure(['def Func(arg: number = "text")', 'enddef'], 'E1013: argument 1: type mismatch, expected number but got string') enddef func Test_call_default_args_from_func() --- 83,99 ---- assert_equal('one', MyDefaultArgs('one')) assert_fails('call MyDefaultArgs("one", "two")', 'E118:') ! CheckScriptFailure(['def Func(arg: number = asdf)', 'enddef'], 'E1001:') ! CheckScriptFailure(['def Func(arg: number = "text")', 'enddef'], 'E1013: argument 1: type mismatch, expected number but got string') ! enddef ! ! def Test_nested_function() ! def Nested(arg: string): string ! return 'nested ' .. arg ! enddef ! assert_equal('nested function', Nested('function')) ! ! CheckDefFailure(['func Nested()', 'endfunc'], 'E1086:') enddef func Test_call_default_args_from_func() *************** *** 721,725 **** --- 718,730 ---- unlet g:UseVararg enddef + def Test_nested_closure() + let local = 'text' + def Closure(arg: string): string + return local .. arg + enddef + assert_equal('text!!!', Closure('!!!')) + enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker *** ../vim-8.2.0694/src/version.c 2020-05-03 22:57:26.973427368 +0200 --- src/version.c 2020-05-04 20:50:17.036641136 +0200 *************** *** 748,749 **** --- 748,751 ---- { /* Add new patch number below this line */ + /**/ + 695, /**/ -- hundred-and-one symptoms of being an internet addict: 43. You tell the kids they can't use the computer because "Daddy's got work to do" and you don't even have a job. /// Bram Moolenaar -- Bram@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 ///