To: vim_dev@googlegroups.com Subject: Patch 8.2.4804 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.4804 Problem: Expression in heredoc doesn't work for compiled function. Solution: Implement compiling the heredoc expressions. (Yegappan Lakshmanan, closes #10232) Files: runtime/doc/eval.txt, src/evalvars.c, src/proto/evalvars.pro, src/ex_getln.c, src/vim9compile.c, src/proto/vim9compile.pro, src/testdir/test_vim9_assign.vim *** ../vim-8.2.4803/runtime/doc/eval.txt 2022-04-17 12:46:50.101294003 +0100 --- runtime/doc/eval.txt 2022-04-21 23:19:41.837086804 +0100 *************** *** 3210,3217 **** expression evaluation fails, then the assignment fails. once the "`=" has been found {expr} and a backtick must follow. {expr} cannot be empty. - Currenty, in a compiled function {expr} is evaluated - when compiling the function, THIS WILL CHANGE. {endmarker} must not contain white space. {endmarker} cannot start with a lower case character. --- 3247,3252 ---- *** ../vim-8.2.4803/src/evalvars.c 2022-04-18 15:45:19.704436521 +0100 --- src/evalvars.c 2022-04-21 23:25:57.708784208 +0100 *************** *** 673,688 **** * * The {marker} is a string. If the optional 'trim' word is supplied before the * marker, then the leading indentation before the lines (matching the ! * indentation in the 'cmd' line) is stripped. * * When getting lines for an embedded script (e.g. python, lua, perl, ruby, ! * tcl, mzscheme), script_get is set to TRUE. In this case, if the marker is * missing, then '.' is accepted as a marker. * * Returns a List with {lines} or NULL on failure. */ list_T * ! heredoc_get(exarg_T *eap, char_u *cmd, int script_get) { char_u *theline = NULL; char_u *marker; --- 673,693 ---- * * The {marker} is a string. If the optional 'trim' word is supplied before the * marker, then the leading indentation before the lines (matching the ! * indentation in the "cmd" line) is stripped. * * When getting lines for an embedded script (e.g. python, lua, perl, ruby, ! * tcl, mzscheme), "script_get" is set to TRUE. In this case, if the marker is * missing, then '.' is accepted as a marker. * + * When compiling a heredoc assignment to a variable in a Vim9 def function, + * "vim9compile" is set to TRUE. In this case, instead of generating a list of + * string values from the heredoc, vim9 instructions are generated. On success + * the returned list will be empty. + * * Returns a List with {lines} or NULL on failure. */ list_T * ! heredoc_get(exarg_T *eap, char_u *cmd, int script_get, int vim9compile) { char_u *theline = NULL; char_u *marker; *************** *** 696,701 **** --- 701,708 ---- int comment_char = in_vim9script() ? '#' : '"'; int evalstr = FALSE; int eval_failed = FALSE; + cctx_T *cctx = vim9compile ? eap->cookie : NULL; + int count = 0; if (eap->getline == NULL) { *************** *** 816,840 **** break; str = theline + ti; ! if (evalstr) { ! str = eval_all_expr_in_str(str); ! if (str == NULL) { ! // expression evaluation failed ! eval_failed = TRUE; ! continue; } ! vim_free(theline); ! theline = str; } ! if (list_append_string(l, str, -1) == FAIL) ! break; } vim_free(theline); vim_free(text_indent); if (eval_failed) { // expression evaluation in the heredoc failed --- 823,863 ---- break; str = theline + ti; ! if (vim9compile) { ! if (compile_heredoc_string(str, evalstr, cctx) == FAIL) { ! vim_free(theline); ! vim_free(text_indent); ! return FAIL; } ! count++; } + else + { + if (evalstr) + { + str = eval_all_expr_in_str(str); + if (str == NULL) + { + // expression evaluation failed + eval_failed = TRUE; + continue; + } + vim_free(theline); + theline = str; + } ! if (list_append_string(l, str, -1) == FAIL) ! break; ! } } vim_free(theline); vim_free(text_indent); + if (vim9compile && cctx->ctx_skip != SKIP_YES && !eval_failed) + generate_NEWLIST(cctx, count, FALSE); + if (eval_failed) { // expression evaluation in the heredoc failed *************** *** 986,992 **** long cur_lnum = SOURCING_LNUM; // HERE document ! l = heredoc_get(eap, expr + 3, FALSE); if (l != NULL) { rettv_list_set(&rettv, l); --- 1009,1015 ---- long cur_lnum = SOURCING_LNUM; // HERE document ! l = heredoc_get(eap, expr + 3, FALSE, FALSE); if (l != NULL) { rettv_list_set(&rettv, l); *** ../vim-8.2.4803/src/proto/evalvars.pro 2022-03-20 17:46:01.797053490 +0000 --- src/proto/evalvars.pro 2022-04-21 23:19:41.837086804 +0100 *************** *** 13,19 **** int get_spellword(list_T *list, char_u **pp); void prepare_vimvar(int idx, typval_T *save_tv); void restore_vimvar(int idx, typval_T *save_tv); ! list_T *heredoc_get(exarg_T *eap, char_u *cmd, int script_get); void ex_var(exarg_T *eap); void ex_let(exarg_T *eap); int ex_let_vars(char_u *arg_start, typval_T *tv, int copy, int semicolon, int var_count, int flags, char_u *op); --- 13,19 ---- int get_spellword(list_T *list, char_u **pp); void prepare_vimvar(int idx, typval_T *save_tv); void restore_vimvar(int idx, typval_T *save_tv); ! list_T *heredoc_get(exarg_T *eap, char_u *cmd, int script_get, int vim9compile); void ex_var(exarg_T *eap); void ex_let(exarg_T *eap); int ex_let_vars(char_u *arg_start, typval_T *tv, int copy, int semicolon, int var_count, int flags, char_u *op); *** ../vim-8.2.4803/src/ex_getln.c 2022-04-15 13:53:30.048708690 +0100 --- src/ex_getln.c 2022-04-21 23:19:41.837086804 +0100 *************** *** 4605,4611 **** return NULL; cmd += 2; ! l = heredoc_get(eap, cmd, TRUE); if (l == NULL) return NULL; --- 4605,4611 ---- return NULL; cmd += 2; ! l = heredoc_get(eap, cmd, TRUE, FALSE); if (l == NULL) return NULL; *** ../vim-8.2.4803/src/vim9compile.c 2022-04-02 19:43:53.927491819 +0100 --- src/vim9compile.c 2022-04-21 23:28:38.244637908 +0100 *************** *** 595,600 **** --- 595,601 ---- /* * Find "name" in imported items of the current script. + * If "len" is 0 use any length that works. * If "load" is TRUE and the script was not loaded yet, load it now. */ imported_T * *************** *** 968,973 **** --- 969,1051 ---- } /* + * Compile a heredoc string "str" (either containing a literal string or a mix + * of literal strings and Vim expressions of the form `=`). This is used + * when compiling a heredoc assignment to a variable in a Vim9 def function. + * Vim9 instructions are generated to push strings, evaluate expressions, + * concatenate them and create a list of lines. When "evalstr" is TRUE, Vim + * expressions in "str" are evaluated. + */ + int + compile_heredoc_string(char_u *str, int evalstr, cctx_T *cctx) + { + char_u *p; + char_u *val; + + if (cctx->ctx_skip == SKIP_YES) + return OK; + + if (evalstr && (p = (char_u *)strstr((char *)str, "`=")) != NULL) + { + char_u *start = str; + + // Need to evaluate expressions of the form `=` in the string. + // Split the string into literal strings and Vim expressions and + // generate instructions to concatenate the literal strings and the + // result of evaluating the Vim expressions. + val = vim_strsave((char_u *)""); + generate_PUSHS(cctx, &val); + + for (;;) + { + if (p > start) + { + // literal string before the expression + val = vim_strnsave(start, p - start); + generate_PUSHS(cctx, &val); + generate_instr_drop(cctx, ISN_CONCAT, 1); + } + p += 2; + + // evaluate the Vim expression and convert the result to string. + if (compile_expr0(&p, cctx) == FAIL) + return FAIL; + may_generate_2STRING(-1, TRUE, cctx); + generate_instr_drop(cctx, ISN_CONCAT, 1); + + p = skipwhite(p); + if (*p != '`') + { + emsg(_(e_missing_backtick)); + return FAIL; + } + start = p + 1; + + p = (char_u *)strstr((char *)start, "`="); + if (p == NULL) + { + // no more Vim expressions left to process + if (*skipwhite(start) != NUL) + { + val = vim_strsave(start); + generate_PUSHS(cctx, &val); + generate_instr_drop(cctx, ISN_CONCAT, 1); + } + break; + } + } + } + else + { + // literal string + val = vim_strsave(str); + generate_PUSHS(cctx, &val); + } + + return OK; + } + + /* * Return the length of an assignment operator, or zero if there isn't one. */ int *************** *** 1946,1970 **** if (heredoc) { list_T *l; - listitem_T *li; // [let] varname =<< [trim] {end} eap->getline = exarg_getline; eap->cookie = cctx; ! l = heredoc_get(eap, op + 3, FALSE); if (l == NULL) return NULL; - if (cctx->ctx_skip != SKIP_YES) - { - // Push each line and the create the list. - FOR_ALL_LIST_ITEMS(l, li) - { - generate_PUSHS(cctx, &li->li_tv.vval.v_string); - li->li_tv.vval.v_string = NULL; - } - generate_NEWLIST(cctx, l->lv_len, FALSE); - } list_free(l); p += STRLEN(p); end = p; --- 2024,2037 ---- if (heredoc) { list_T *l; // [let] varname =<< [trim] {end} eap->getline = exarg_getline; eap->cookie = cctx; ! l = heredoc_get(eap, op + 3, FALSE, TRUE); if (l == NULL) return NULL; list_free(l); p += STRLEN(p); end = p; *** ../vim-8.2.4803/src/proto/vim9compile.pro 2022-04-02 19:43:53.927491819 +0100 --- src/proto/vim9compile.pro 2022-04-21 23:19:41.837086804 +0100 *************** *** 16,21 **** --- 16,22 ---- int may_get_next_line_error(char_u *whitep, char_u **arg, cctx_T *cctx); void fill_exarg_from_cctx(exarg_T *eap, cctx_T *cctx); int func_needs_compiling(ufunc_T *ufunc, compiletype_T compile_type); + int compile_heredoc_string(char_u *str, int evalstr, cctx_T *cctx); int assignment_len(char_u *p, int *heredoc); void vim9_declare_error(char_u *name); int get_var_dest(char_u *name, assign_dest_T *dest, cmdidx_T cmdidx, int *option_scope, int *vimvaridx, type_T **type, cctx_T *cctx); *** ../vim-8.2.4803/src/testdir/test_vim9_assign.vim 2022-04-17 12:46:50.101294003 +0100 --- src/testdir/test_vim9_assign.vim 2022-04-21 23:19:41.841086802 +0100 *************** *** 1821,1830 **** enddef def Test_heredoc() ! var lines =<< trim END # comment ! text END ! assert_equal(['text'], lines) v9.CheckDefFailure(['var lines =<< trim END X', 'END'], 'E488:') v9.CheckDefFailure(['var lines =<< trim END " comment', 'END'], 'E488:') --- 1821,1851 ---- enddef def Test_heredoc() ! # simple heredoc ! var lines =<< trim END ! var text =<< trim TEXT # comment ! abc ! TEXT ! assert_equal(['abc'], text) ! END ! v9.CheckDefAndScriptSuccess(lines) ! ! # empty heredoc ! lines =<< trim END ! var text =<< trim TEXT ! TEXT ! assert_equal([], text) END ! v9.CheckDefAndScriptSuccess(lines) ! ! # heredoc with a single empty line ! lines =<< trim END ! var text =<< trim TEXT ! ! TEXT ! assert_equal([''], text) ! END ! v9.CheckDefAndScriptSuccess(lines) v9.CheckDefFailure(['var lines =<< trim END X', 'END'], 'E488:') v9.CheckDefFailure(['var lines =<< trim END " comment', 'END'], 'E488:') *************** *** 2642,2692 **** " Test for heredoc with Vim expressions. " This messes up highlighting, keep it near the end. def Test_heredoc_expr() ! var code =<< trim eval END ! var a = `=5 + 10` ! var b = `=min([10, 6])` + `=max([4, 6])` ! END ! assert_equal(['var a = 15', 'var b = 6 + 6'], code) ! code =<< eval trim END ! var s = "`=$SOME_ENV_VAR`" ! END ! assert_equal(['var s = "somemore"'], code) ! code =<< eval END ! var s = "`=$SOME_ENV_VAR`" ! END ! assert_equal([' var s = "somemore"'], code) ! code =<< eval trim END ! let a = `abc` ! let b = `=g:someVar` ! let c = ` ! END ! assert_equal(['let a = `abc`', 'let b = X', 'let c = `'], code) ! var lines =<< trim LINES var text =<< eval trim END let b = `= END LINES ! v9.CheckDefAndScriptFailure(lines, 'E1083:') lines =<< trim LINES var text =<< eval trim END let b = `=abc END LINES ! v9.CheckDefAndScriptFailure(lines, 'E1083:') lines =<< trim LINES var text =<< eval trim END let b = `=` END LINES ! v9.CheckDefAndScriptFailure(lines, 'E15:') enddef - - " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker --- 2663,2730 ---- " Test for heredoc with Vim expressions. " This messes up highlighting, keep it near the end. def Test_heredoc_expr() ! var lines =<< trim CODE ! var s = "local" ! var a1 = "1" ! var a2 = "2" ! var a3 = "3" ! var a4 = "" ! var code =<< trim eval END ! var a = `=5 + 10` ! var b = `=min([10, 6])` + `=max([4, 6])` ! var c = "`=s`" ! var d = x`=a1`x`=a2`x`=a3`x`=a4` ! END ! assert_equal(['var a = 15', 'var b = 6 + 6', 'var c = "local"', 'var d = x1x2x3x'], code) ! CODE ! v9.CheckDefAndScriptSuccess(lines) ! lines =<< trim CODE ! var code =<< eval trim END ! var s = "`=$SOME_ENV_VAR`" ! END ! assert_equal(['var s = "somemore"'], code) ! CODE ! v9.CheckDefAndScriptSuccess(lines) ! lines =<< trim CODE ! var code =<< eval END ! var s = "`=$SOME_ENV_VAR`" ! END ! assert_equal([' var s = "somemore"'], code) ! CODE ! v9.CheckDefAndScriptSuccess(lines) ! lines =<< trim CODE ! var code =<< eval trim END ! let a = `abc` ! let b = `=g:someVar` ! let c = ` ! END ! assert_equal(['let a = `abc`', 'let b = X', 'let c = `'], code) ! CODE ! v9.CheckDefAndScriptSuccess(lines) ! lines =<< trim LINES var text =<< eval trim END let b = `= END LINES ! v9.CheckDefAndScriptFailure(lines, ['E1143: Empty expression: ""', 'E1083: Missing backtick']) lines =<< trim LINES var text =<< eval trim END let b = `=abc END LINES ! v9.CheckDefAndScriptFailure(lines, ['E1001: Variable not found: abc', 'E1083: Missing backtick']) lines =<< trim LINES var text =<< eval trim END let b = `=` END LINES ! v9.CheckDefAndScriptFailure(lines, ['E1015: Name expected: `', 'E15: Invalid expression: "`"']) enddef " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker *** ../vim-8.2.4803/src/version.c 2022-04-21 22:52:07.062317208 +0100 --- src/version.c 2022-04-21 23:22:38.984952863 +0100 *************** *** 748,749 **** --- 748,751 ---- { /* Add new patch number below this line */ + /**/ + 4804, /**/ -- To define recursion, we must first define recursion. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///