Skip to content

Commit 2067733

Browse files
committed
patch 8.2.2951: Vim9: cannot use heredoc for :python, :lua, etc.
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.
1 parent c64ed2b commit 2067733

7 files changed

Lines changed: 182 additions & 15 deletions

File tree

src/testdir/test_vim9_disassemble.vim

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,23 @@ def Test_disassemble_exec_expr()
121121
res)
122122
enddef
123123

124+
if has('python3')
125+
def s:PyHeredoc()
126+
python3 << EOF
127+
print('hello')
128+
EOF
129+
enddef
130+
131+
def Test_disassemble_python_heredoc()
132+
var res = execute('disass s:PyHeredoc')
133+
assert_match('<SNR>\d*_PyHeredoc.*' ..
134+
" python3 << EOF^@ print('hello')^@EOF\\_s*" ..
135+
'\d EXEC_SPLIT python3 << EOF^@ print(''hello'')^@EOF\_s*' ..
136+
'\d RETURN 0',
137+
res)
138+
enddef
139+
endif
140+
124141
def s:Substitute()
125142
var expr = "abc"
126143
:%s/a/\=expr/&g#c

src/testdir/test_vim9_func.vim

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2758,5 +2758,33 @@ def Test_closing_brace_at_start_of_line()
27582758
call CheckDefAndScriptSuccess(lines)
27592759
enddef
27602760

2761+
if has('python3')
2762+
def Test_python3_heredoc()
2763+
py3 << trim EOF
2764+
import vim
2765+
vim.vars['didit'] = 'yes'
2766+
EOF
2767+
assert_equal('yes', g:didit)
2768+
2769+
python3 << trim EOF
2770+
import vim
2771+
vim.vars['didit'] = 'again'
2772+
EOF
2773+
assert_equal('again', g:didit)
2774+
enddef
2775+
endif
2776+
2777+
" This messes up syntax highlight, keep near the end.
2778+
if has('lua')
2779+
def Test_lua_heredoc()
2780+
g:d = {}
2781+
lua << trim EOF
2782+
x = vim.eval('g:d')
2783+
x['key'] = 'val'
2784+
EOF
2785+
assert_equal('val', g:d.key)
2786+
enddef
2787+
endif
2788+
27612789

27622790
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker

src/userfunc.c

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -631,8 +631,12 @@ get_function_body(
631631
char_u *skip_until = NULL;
632632
int ret = FAIL;
633633
int is_heredoc = FALSE;
634+
int heredoc_concat_len = 0;
635+
garray_T heredoc_ga;
634636
char_u *heredoc_trimmed = NULL;
635637

638+
ga_init2(&heredoc_ga, 1, 500);
639+
636640
// Detect having skipped over comment lines to find the return
637641
// type. Add NULL lines to keep the line count correct.
638642
sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie);
@@ -733,6 +737,20 @@ get_function_body(
733737
getline_options = vim9_function
734738
? GETLINE_CONCAT_CONTBAR : GETLINE_CONCAT_CONT;
735739
is_heredoc = FALSE;
740+
741+
if (heredoc_concat_len > 0)
742+
{
743+
// Replace the starting line with all the concatenated
744+
// lines.
745+
ga_concat(&heredoc_ga, theline);
746+
vim_free(((char_u **)(newlines->ga_data))[
747+
heredoc_concat_len - 1]);
748+
((char_u **)(newlines->ga_data))[
749+
heredoc_concat_len - 1] = heredoc_ga.ga_data;
750+
ga_init(&heredoc_ga);
751+
heredoc_concat_len = 0;
752+
theline += STRLEN(theline); // skip the "EOF"
753+
}
736754
}
737755
}
738756
}
@@ -886,6 +904,8 @@ get_function_body(
886904
skip_until = vim_strnsave(p, skiptowhite(p) - p);
887905
getline_options = GETLINE_NONE;
888906
is_heredoc = TRUE;
907+
if (eap->cmdidx == CMD_def)
908+
heredoc_concat_len = newlines->ga_len + 1;
889909
}
890910

891911
// Check for ":cmd v =<< [trim] EOF"
@@ -928,10 +948,21 @@ get_function_body(
928948
if (ga_grow(newlines, 1 + sourcing_lnum_off) == FAIL)
929949
goto theend;
930950

931-
// Copy the line to newly allocated memory. get_one_sourceline()
932-
// allocates 250 bytes per line, this saves 80% on average. The cost
933-
// is an extra alloc/free.
934-
p = vim_strsave(theline);
951+
if (heredoc_concat_len > 0)
952+
{
953+
// For a :def function "python << EOF" concatenats all the lines,
954+
// to be used for the instruction later.
955+
ga_concat(&heredoc_ga, theline);
956+
ga_concat(&heredoc_ga, (char_u *)"\n");
957+
p = vim_strsave((char_u *)"");
958+
}
959+
else
960+
{
961+
// Copy the line to newly allocated memory. get_one_sourceline()
962+
// allocates 250 bytes per line, this saves 80% on average. The
963+
// cost is an extra alloc/free.
964+
p = vim_strsave(theline);
965+
}
935966
if (p == NULL)
936967
goto theend;
937968
((char_u **)(newlines->ga_data))[newlines->ga_len++] = p;
@@ -953,6 +984,7 @@ get_function_body(
953984
theend:
954985
vim_free(skip_until);
955986
vim_free(heredoc_trimmed);
987+
vim_free(heredoc_ga.ga_data);
956988
need_wait_return |= saved_wait_return;
957989
return ret;
958990
}
@@ -1436,6 +1468,7 @@ deref_func_name(
14361468

14371469
cc = name[*lenp];
14381470
name[*lenp] = NUL;
1471+
14391472
v = find_var(name, &ht, no_autoload);
14401473
name[*lenp] = cc;
14411474
if (v != NULL)

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,8 @@ static char *(features[]) =
750750

751751
static int included_patches[] =
752752
{ /* Add new patch number below this line */
753+
/**/
754+
2951,
753755
/**/
754756
2950,
755757
/**/

src/vim9.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
typedef enum {
1515
ISN_EXEC, // execute Ex command line isn_arg.string
1616
ISN_EXECCONCAT, // execute Ex command from isn_arg.number items on stack
17+
ISN_EXEC_SPLIT, // execute Ex command from isn_arg.string split at NL
1718
ISN_LEGACY_EVAL, // evaluate expression isn_arg.string with legacy syntax.
1819
ISN_ECHO, // echo isn_arg.echo.echo_count items on top of stack
1920
ISN_EXECUTE, // execute Ex commands isn_arg.number items on top of stack

src/vim9compile.c

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8668,6 +8668,29 @@ compile_exec(char_u *line, exarg_T *eap, cctx_T *cctx)
86688668
return nextcmd;
86698669
}
86708670

8671+
/*
8672+
* A script command with heredoc, e.g.
8673+
* ruby << EOF
8674+
* command
8675+
* EOF
8676+
* Has been turned into one long line with NL characters by
8677+
* get_function_body():
8678+
* ruby << EOF<NL> command<NL>EOF
8679+
*/
8680+
static char_u *
8681+
compile_script(char_u *line, cctx_T *cctx)
8682+
{
8683+
if (cctx->ctx_skip != SKIP_YES)
8684+
{
8685+
isn_T *isn;
8686+
8687+
if ((isn = generate_instr(cctx, ISN_EXEC_SPLIT)) == NULL)
8688+
return NULL;
8689+
isn->isn_arg.string = vim_strsave(line);
8690+
}
8691+
return (char_u *)"";
8692+
}
8693+
86718694

86728695
/*
86738696
* :s/pat/repl/
@@ -9480,18 +9503,28 @@ compile_def_function(
94809503
line = (char_u *)"";
94819504
break;
94829505

9483-
default:
9484-
if (cctx.ctx_skip == SKIP_YES)
9485-
{
9486-
// We don't check for a next command here.
9487-
line = (char_u *)"";
9488-
}
9489-
else
9490-
{
9491-
// Not recognized, execute with do_cmdline_cmd().
9492-
ea.arg = p;
9506+
case CMD_lua:
9507+
case CMD_mzscheme:
9508+
case CMD_perl:
9509+
case CMD_py3:
9510+
case CMD_python3:
9511+
case CMD_python:
9512+
case CMD_pythonx:
9513+
case CMD_ruby:
9514+
case CMD_tcl:
9515+
ea.arg = p;
9516+
if (vim_strchr(line, '\n') == NULL)
94939517
line = compile_exec(line, &ea, &cctx);
9494-
}
9518+
else
9519+
// heredoc lines have been concatenated with NL
9520+
// characters in get_function_body()
9521+
line = compile_script(line, &cctx);
9522+
break;
9523+
9524+
default:
9525+
// Not recognized, execute with do_cmdline_cmd().
9526+
ea.arg = p;
9527+
line = compile_exec(line, &ea, &cctx);
94959528
break;
94969529
}
94979530
nextline:
@@ -9674,6 +9707,7 @@ delete_instr(isn_T *isn)
96749707
{
96759708
case ISN_DEF:
96769709
case ISN_EXEC:
9710+
case ISN_EXEC_SPLIT:
96779711
case ISN_LEGACY_EVAL:
96789712
case ISN_LOADAUTO:
96799713
case ISN_LOADB:

src/vim9execute.c

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,6 +1213,37 @@ get_script_svar(scriptref_T *sref, ectx_T *ectx)
12131213
return sv;
12141214
}
12151215

1216+
/*
1217+
* Function passed to do_cmdline() for splitting a script joined by NL
1218+
* characters.
1219+
*/
1220+
static char_u *
1221+
get_split_sourceline(
1222+
int c UNUSED,
1223+
void *cookie,
1224+
int indent UNUSED,
1225+
getline_opt_T options UNUSED)
1226+
{
1227+
source_cookie_T *sp = (source_cookie_T *)cookie;
1228+
char_u *p;
1229+
char_u *line;
1230+
1231+
if (*sp->nextline == NUL)
1232+
return NULL;
1233+
p = vim_strchr(sp->nextline, '\n');
1234+
if (p == NULL)
1235+
{
1236+
line = vim_strsave(sp->nextline);
1237+
sp->nextline += STRLEN(sp->nextline);
1238+
}
1239+
else
1240+
{
1241+
line = vim_strnsave(sp->nextline, p - sp->nextline);
1242+
sp->nextline = p + 1;
1243+
}
1244+
return line;
1245+
}
1246+
12161247
/*
12171248
* Execute a function by "name".
12181249
* This can be a builtin function, user function or a funcref.
@@ -1425,6 +1456,24 @@ exec_instructions(ectx_T *ectx)
14251456
}
14261457
break;
14271458

1459+
// execute Ex command line split at NL characters.
1460+
case ISN_EXEC_SPLIT:
1461+
{
1462+
source_cookie_T cookie;
1463+
1464+
SOURCING_LNUM = iptr->isn_lnum;
1465+
CLEAR_FIELD(cookie);
1466+
cookie.sourcing_lnum = iptr->isn_lnum - 1;
1467+
cookie.nextline = iptr->isn_arg.string;
1468+
if (do_cmdline(get_split_sourceline(0, &cookie, 0, 0),
1469+
get_split_sourceline, &cookie,
1470+
DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED)
1471+
== FAIL
1472+
|| did_emsg)
1473+
goto on_error;
1474+
}
1475+
break;
1476+
14281477
// Evaluate an expression with legacy syntax, push it onto the
14291478
// stack.
14301479
case ISN_LEGACY_EVAL:
@@ -4536,6 +4585,9 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
45364585
case ISN_EXEC:
45374586
smsg("%s%4d EXEC %s", pfx, current, iptr->isn_arg.string);
45384587
break;
4588+
case ISN_EXEC_SPLIT:
4589+
smsg("%s%4d EXEC_SPLIT %s", pfx, current, iptr->isn_arg.string);
4590+
break;
45394591
case ISN_LEGACY_EVAL:
45404592
smsg("%s%4d EVAL legacy %s", pfx, current,
45414593
iptr->isn_arg.string);

0 commit comments

Comments
 (0)