summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2021-06-06 17:02:53 +0200
committerBram Moolenaar <Bram@vim.org>2021-06-06 17:02:53 +0200
commit2067733b5c76e996238af938af36b8196366b7ce (patch)
tree07c435f04c6f6747e9abfe8e796a6c648cb687d1
parentc64ed2b714e605930e7c4102d4e80a2b1d9ff9bf (diff)
patch 8.2.2951: Vim9: cannot use heredoc for :python, :lua, etc.v8.2.2951
Problem: Vim9: cannot use heredoc in :def function for :python, :lua, etc. Solution: Concatenate the heredoc lines and pass them in the ISN_EXEC_SPLIT instruction.
-rw-r--r--src/testdir/test_vim9_disassemble.vim17
-rw-r--r--src/testdir/test_vim9_func.vim28
-rw-r--r--src/userfunc.c41
-rw-r--r--src/version.c2
-rw-r--r--src/vim9.h1
-rw-r--r--src/vim9compile.c56
-rw-r--r--src/vim9execute.c52
7 files changed, 182 insertions, 15 deletions
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index f266cbe7c8..965399bca1 100644
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -121,6 +121,23 @@ def Test_disassemble_exec_expr()
res)
enddef
+if has('python3')
+ def s:PyHeredoc()
+ python3 << EOF
+ print('hello')
+EOF
+ enddef
+
+ def Test_disassemble_python_heredoc()
+ var res = execute('disass s:PyHeredoc')
+ assert_match('<SNR>\d*_PyHeredoc.*' ..
+ " python3 << EOF^@ print('hello')^@EOF\\_s*" ..
+ '\d EXEC_SPLIT python3 << EOF^@ print(''hello'')^@EOF\_s*' ..
+ '\d RETURN 0',
+ res)
+ enddef
+endif
+
def s:Substitute()
var expr = "abc"
:%s/a/\=expr/&g#c
diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim
index 9f0cd3f895..7e21b37883 100644
--- a/src/testdir/test_vim9_func.vim
+++ b/src/testdir/test_vim9_func.vim
@@ -2758,5 +2758,33 @@ def Test_closing_brace_at_start_of_line()
call CheckDefAndScriptSuccess(lines)
enddef
+if has('python3')
+ def Test_python3_heredoc()
+ py3 << trim EOF
+ import vim
+ vim.vars['didit'] = 'yes'
+ EOF
+ assert_equal('yes', g:didit)
+
+ python3 << trim EOF
+ import vim
+ vim.vars['didit'] = 'again'
+ EOF
+ assert_equal('again', g:didit)
+ enddef
+endif
+
+" This messes up syntax highlight, keep near the end.
+if has('lua')
+ def Test_lua_heredoc()
+ g:d = {}
+ lua << trim EOF
+ x = vim.eval('g:d')
+ x['key'] = 'val'
+ EOF
+ assert_equal('val', g:d.key)
+ enddef
+endif
+
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/userfunc.c b/src/userfunc.c
index f5d9063028..64e815d042 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -631,8 +631,12 @@ get_function_body(
char_u *skip_until = NULL;
int ret = FAIL;
int is_heredoc = FALSE;
+ int heredoc_concat_len = 0;
+ garray_T heredoc_ga;
char_u *heredoc_trimmed = NULL;
+ ga_init2(&heredoc_ga, 1, 500);
+
// Detect having skipped over comment lines to find the return
// type. Add NULL lines to keep the line count correct.
sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie);
@@ -733,6 +737,20 @@ get_function_body(
getline_options = vim9_function
? GETLINE_CONCAT_CONTBAR : GETLINE_CONCAT_CONT;
is_heredoc = FALSE;
+
+ if (heredoc_concat_len > 0)
+ {
+ // Replace the starting line with all the concatenated
+ // lines.
+ ga_concat(&heredoc_ga, theline);
+ vim_free(((char_u **)(newlines->ga_data))[
+ heredoc_concat_len - 1]);
+ ((char_u **)(newlines->ga_data))[
+ heredoc_concat_len - 1] = heredoc_ga.ga_data;
+ ga_init(&heredoc_ga);
+ heredoc_concat_len = 0;
+ theline += STRLEN(theline); // skip the "EOF"
+ }
}
}
}
@@ -886,6 +904,8 @@ get_function_body(
skip_until = vim_strnsave(p, skiptowhite(p) - p);
getline_options = GETLINE_NONE;
is_heredoc = TRUE;
+ if (eap->cmdidx == CMD_def)
+ heredoc_concat_len = newlines->ga_len + 1;
}
// Check for ":cmd v =<< [trim] EOF"
@@ -928,10 +948,21 @@ get_function_body(
if (ga_grow(newlines, 1 + sourcing_lnum_off) == FAIL)
goto theend;
- // Copy the line to newly allocated memory. get_one_sourceline()
- // allocates 250 bytes per line, this saves 80% on average. The cost
- // is an extra alloc/free.
- p = vim_strsave(theline);
+ if (heredoc_concat_len > 0)
+ {
+ // For a :def function "python << EOF" concatenats all the lines,
+ // to be used for the instruction later.
+ ga_concat(&heredoc_ga, theline);
+ ga_concat(&heredoc_ga, (char_u *)"\n");
+ p = vim_strsave((char_u *)"");
+ }
+ else
+ {
+ // Copy the line to newly allocated memory. get_one_sourceline()
+ // allocates 250 bytes per line, this saves 80% on average. The
+ // cost is an extra alloc/free.
+ p = vim_strsave(theline);
+ }
if (p == NULL)
goto theend;
((char_u **)(newlines->ga_data))[newlines->ga_len++] = p;
@@ -953,6 +984,7 @@ get_function_body(
theend:
vim_free(skip_until);
vim_free(heredoc_trimmed);
+ vim_free(heredoc_ga.ga_data);
need_wait_return |= saved_wait_return;
return ret;
}
@@ -1436,6 +1468,7 @@ deref_func_name(
cc = name[*lenp];
name[*lenp] = NUL;
+
v = find_var(name, &ht, no_autoload);
name[*lenp] = cc;
if (v != NULL)
diff --git a/src/version.c b/src/version.c
index 06340e31af..f995518798 100644
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 2951,
+/**/
2950,
/**/
2949,
diff --git a/src/vim9.h b/src/vim9.h
index 6bc277d9bf..d4d3b7d6c1 100644
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -14,6 +14,7 @@
typedef enum {
ISN_EXEC, // execute Ex command line isn_arg.string
ISN_EXECCONCAT, // execute Ex command from isn_arg.number items on stack
+ ISN_EXEC_SPLIT, // execute Ex command from isn_arg.string split at NL
ISN_LEGACY_EVAL, // evaluate expression isn_arg.string with legacy syntax.
ISN_ECHO, // echo isn_arg.echo.echo_count items on top of stack
ISN_EXECUTE, // execute Ex commands isn_arg.number items on top of stack
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 573fa4365b..0c73433d5f 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -8668,6 +8668,29 @@ theend:
return nextcmd;
}
+/*
+ * A script command with heredoc, e.g.
+ * ruby << EOF
+ * command
+ * EOF
+ * Has been turned into one long line with NL characters by
+ * get_function_body():
+ * ruby << EOF<NL> command<NL>EOF
+ */
+ static char_u *
+compile_script(char_u *line, cctx_T *cctx)
+{
+ if (cctx->ctx_skip != SKIP_YES)
+ {
+ isn_T *isn;
+
+ if ((isn = generate_instr(cctx, ISN_EXEC_SPLIT)) == NULL)
+ return NULL;
+ isn->isn_arg.string = vim_strsave(line);
+ }
+ return (char_u *)"";
+}
+
/*
* :s/pat/repl/
@@ -9480,18 +9503,28 @@ compile_def_function(
line = (char_u *)"";
break;
- default:
- if (cctx.ctx_skip == SKIP_YES)
- {
- // We don't check for a next command here.
- line = (char_u *)"";
- }
- else
- {
- // Not recognized, execute with do_cmdline_cmd().
- ea.arg = p;
+ case CMD_lua:
+ case CMD_mzscheme:
+ case CMD_perl:
+ case CMD_py3:
+ case CMD_python3:
+ case CMD_python:
+ case CMD_pythonx:
+ case CMD_ruby:
+ case CMD_tcl:
+ ea.arg = p;
+ if (vim_strchr(line, '\n') == NULL)
line = compile_exec(line, &ea, &cctx);
- }
+ else
+ // heredoc lines have been concatenated with NL
+ // characters in get_function_body()
+ line = compile_script(line, &cctx);
+ break;
+
+ default:
+ // Not recognized, execute with do_cmdline_cmd().
+ ea.arg = p;
+ line = compile_exec(line, &ea, &cctx);
break;
}
nextline:
@@ -9674,6 +9707,7 @@ delete_instr(isn_T *isn)
{
case ISN_DEF:
case ISN_EXEC:
+ case ISN_EXEC_SPLIT:
case ISN_LEGACY_EVAL:
case ISN_LOADAUTO:
case ISN_LOADB:
diff --git a/src/vim9execute.c b/src/vim9execute.c
index e870a279c0..1f2f7357e8 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -1214,6 +1214,37 @@ get_script_svar(scriptref_T *sref, ectx_T *ectx)
}
/*
+ * Function passed to do_cmdline() for splitting a script joined by NL
+ * characters.
+ */
+ static char_u *
+get_split_sourceline(
+ int c UNUSED,
+ void *cookie,
+ int indent UNUSED,
+ getline_opt_T options UNUSED)
+{
+ source_cookie_T *sp = (source_cookie_T *)cookie;
+ char_u *p;
+ char_u *line;
+
+ if (*sp->nextline == NUL)
+ return NULL;
+ p = vim_strchr(sp->nextline, '\n');
+ if (p == NULL)
+ {
+ line = vim_strsave(sp->nextline);
+ sp->nextline += STRLEN(sp->nextline);
+ }
+ else
+ {
+ line = vim_strnsave(sp->nextline, p - sp->nextline);
+ sp->nextline = p + 1;
+ }
+ return line;
+}
+
+/*
* Execute a function by "name".
* This can be a builtin function, user function or a funcref.
* "iptr" can be used to replace the instruction with a more efficient one.
@@ -1425,6 +1456,24 @@ exec_instructions(ectx_T *ectx)
}
break;
+ // execute Ex command line split at NL characters.
+ case ISN_EXEC_SPLIT:
+ {
+ source_cookie_T cookie;
+
+ SOURCING_LNUM = iptr->isn_lnum;
+ CLEAR_FIELD(cookie);
+ cookie.sourcing_lnum = iptr->isn_lnum - 1;
+ cookie.nextline = iptr->isn_arg.string;
+ if (do_cmdline(get_split_sourceline(0, &cookie, 0, 0),
+ get_split_sourceline, &cookie,
+ DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED)
+ == FAIL
+ || did_emsg)
+ goto on_error;
+ }
+ break;
+
// Evaluate an expression with legacy syntax, push it onto the
// stack.
case ISN_LEGACY_EVAL:
@@ -4536,6 +4585,9 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
case ISN_EXEC:
smsg("%s%4d EXEC %s", pfx, current, iptr->isn_arg.string);
break;
+ case ISN_EXEC_SPLIT:
+ smsg("%s%4d EXEC_SPLIT %s", pfx, current, iptr->isn_arg.string);
+ break;
case ISN_LEGACY_EVAL:
smsg("%s%4d EVAL legacy %s", pfx, current,
iptr->isn_arg.string);