This is cmd engine rewrite part XXXXI.
Main goal is to cover bug entry 39289.
To do so we need to: - properly handle the echo of commands when not in interactive mode + good news if that this shares mostly the code we added to rewrite commands to pass them in pipe sub-shells, + also fixing the handling of @ at start of command (there we a bunch of issues: it was only supported at the start of a single command, all the other forms IF/FOR/() are supposed to handle them too) + some of the tests assumed there is some kind of propagation of @ in chained commands; I do believe it's mostly linked to the commands actually run + most of the tests related to echo now pass (expect a few where there are discrepancies but only on the number of spaces inserted). I don't feel like fixing these unless an app actually depends on it, - fix a couple of other issues: + prompt $H actually echo:es \x08\x20\x08 instead of \x08... to ensure that prev char is actually cleaned (manually tested on Windows) + loop variables when not expanded inside a !foo! construct (other env variables were).
Test program from Bug 29389 works as expected, even if output isn't as nice as on windows, mainly because of conhost refresh rate (BZ#56851).
Side note: this is probably the last MR from the cmd engine rewrite saga. There are still open tickets on cmd.exe but most of the structural work should be ok by now.
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/batch.c | 26 +++++++++++------------- programs/cmd/tests/test_builtins.cmd.exp | 2 +- programs/cmd/wcmdmain.c | 2 +- 3 files changed, 14 insertions(+), 16 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index 035f8accc00..e015179239a 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -655,24 +655,22 @@ RETURN_CODE WCMD_call(WCHAR *command) { RETURN_CODE return_code; WCHAR buffer[MAXSTRING]; + WCHAR *start; + WCMD_expand(command, buffer);
+ /* (call) shall return 1, while (call ) returns 0 */ + start = WCMD_skip_leading_spaces(buffer); + if (*start == L'\0') + return_code = errorlevel = start == buffer ? ERROR_INVALID_FUNCTION : NO_ERROR; /* Run other program if no leading ':' */ - if (*command != ':') + else if (*start != ':') { - if (*WCMD_skip_leading_spaces(buffer) == L'\0') - /* FIXME it's incomplete as (call) should return 1, and (call ) should return 0... - * but we need to get the untouched string in command - */ - return_code = errorlevel = NO_ERROR; - else - { - WCMD_call_command(buffer); - /* If the thing we try to run does not exist, call returns 1 */ - if (errorlevel == RETURN_CODE_CANT_LAUNCH) - errorlevel = ERROR_INVALID_FUNCTION; - return_code = errorlevel; - } + WCMD_call_command(start); + /* If the thing we try to run does not exist, call returns 1 */ + if (errorlevel == RETURN_CODE_CANT_LAUNCH) + errorlevel = ERROR_INVALID_FUNCTION; + return_code = errorlevel; } else if (WCMD_is_in_context(NULL)) { diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 1909bb9a8c1..72395eb7007 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -1972,7 +1972,7 @@ Should expand foobaz batfile robinfile 0 -@todo_wine@1 +1 1 1 non-builtin dir diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 869bf3962b9..ec61c7704ca 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -2221,7 +2221,7 @@ RETURN_CODE WCMD_run_builtin_command(int cmd_index, WCHAR *cmd) switch (cmd_index) { case WCMD_CALL: - return_code = WCMD_call(parms_start); + return_code = WCMD_call(&cmd[count]); break; case WCMD_CD: case WCMD_CHDIR:
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/batch.c | 11 +++++++++++ programs/cmd/tests/test_builtins.cmd | 23 ++++++++++++++++++++++- programs/cmd/tests/test_builtins.cmd.exp | 24 +++++++++++++++++++++++- 3 files changed, 56 insertions(+), 2 deletions(-)
diff --git a/programs/cmd/tests/batch.c b/programs/cmd/tests/batch.c index 5c5b0ab7287..c4421aa3f6c 100644 --- a/programs/cmd/tests/batch.c +++ b/programs/cmd/tests/batch.c @@ -207,6 +207,7 @@ static const char *compare_line(const char *out_line, const char *out_end, const { const char *out_ptr = out_line, *exp_ptr = exp_line; const char *err = NULL; + char ch;
static const char pwd_cmd[] = {'@','p','w','d','@'}; static const char drive_cmd[] = {'@','d','r','i','v','e','@'}; @@ -216,6 +217,7 @@ static const char *compare_line(const char *out_line, const char *out_end, const static const char spaces_cmd[] = {'@','s','p','a','c','e','s','@'}; static const char tab_cmd[] = {'@','t','a','b','@'}; static const char formfeed_cmd[] = {'@','f','o','r','m', 'f', 'e', 'e', 'd', '@'}; + static const char hexadecimal_cmd[] = {'@','\','x','0','0', '@'}; static const char or_broken_cmd[] = {'@','o','r','_','b','r','o','k','e','n','@'};
while(exp_ptr < exp_end) { @@ -300,6 +302,15 @@ static const char *compare_line(const char *out_line, const char *out_end, const } else { err = out_end; } + }else if(exp_ptr+sizeof(hexadecimal_cmd) <= exp_end && + parse_hexadecimal(exp_ptr, &ch)) { + exp_ptr += sizeof(hexadecimal_cmd); + if(out_ptr < out_end && *out_ptr == ch) { + out_ptr++; + continue; + } else { + err = out_end; + } }else if(exp_ptr+sizeof(or_broken_cmd) <= exp_end && !memcmp(exp_ptr, or_broken_cmd, sizeof(or_broken_cmd))) { if(out_ptr == out_end) diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index 7d0dc27b1e0..f706a3ef57d 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -31,13 +31,26 @@ echo @tab@word echo @tab@word echo@tab@@tab@word echo @tab@ on @space@ +> nul echo a +if@tab@1 == 2 then @echo a +@rem native stores the keyword (and preserve the case) :-( +IF@tab@1 == 2 ThEn @EchO a +@rem echo is done at execution time +@for %%a in (1 2) do echo %%a +@echo --- +@rem this convoluted code captures ^H inside BS env variable +@for /f %%a in ('"prompt $H&for %%b in (1) do rem"') do @set "BS=%%a" +@echo AA%BS%BB @echo --- @ with chains and brackets (echo the @ character chains until&&@echo we leave the current depth||( echo hidden @echo hidden ))&&echo and can hide brackets||(@echo command hidden)||@(echo brackets hidden) @echo --- - +@set V=@ +%V%echo foo1 +> nul echo a && @echo foo2 +@echo --- @echo off echo off@tab@@space@ @echo noecho1 @@ -1346,6 +1359,14 @@ set WINE_FOO=foo bar if !WINE_FOO!=="" (echo empty) else echo not empty setlocal DisableDelayedExpansion
+echo --- nested expansion +setlocal EnableDelayedExpansion +set WINE_FOO_bar23=foo +set WINE_BAR=bar +set WINE_BAR=bar2 && echo !WINE_FOO_%WINE_BAR%23! +for %%a in (bar) do echo !WINE_FOO_%%a23! +endlocal + echo --- using /V cmd flag echo @echo off> tmp.cmd echo set WINE_FOO=foo>> tmp.cmd diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 72395eb7007..6979c642085 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -88,13 +88,32 @@ word @tab@word
@pwd@>echo @tab@ on @space@@space@ ---- @ with chains and brackets + +@todo_wine@@pwd@>echo a 1>nul@space@ + +@todo_wine@@pwd@>if 1 == 2 then @echo a@space@ + +@todo_wine@@pwd@>IF 1 == 2 ThEn @EchO a@space@ +@todo_wine@ +@todo_wine@@pwd@>echo 1@space@ +@todo_wine@1 +@todo_wine@ +@todo_wine@@pwd@>echo 2@space@ +@todo_wine@2 +@todo_wine@--- +@todo_wine@AA@\x08@BB +@todo_wine@--- @ with chains and brackets
@todo_wine@@pwd@>(echo the @ character chains until && ) && echo and can hide brackets || () ||@space@ @todo_wine@the @ character chains until @todo_wine@we leave the current depth @todo_wine@and can hide brackets @todo_wine@--- +foo1 + +@todo_wine@@pwd@>echo a 1>nul &&@space@ +foo2 +--- noecho1 noecho2 echo3 @@ -967,6 +986,9 @@ gotitright foo !WINE_FOO! not empty +--- nested expansion +foo +@todo_wine@foo --- using /V cmd flag foo foo@or_broken@!WINE_FOO!
From: Eric Pouech epouech@codeweavers.com
Introducing: - new lexer token for the '@' input character,
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/wcmd.h | 1 + programs/cmd/wcmdmain.c | 77 ++++++++++++++++++++++++++--------------- 2 files changed, 50 insertions(+), 28 deletions(-)
diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index c14affb55ae..2fcd7ccc8e7 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -112,6 +112,7 @@ typedef struct _CMD_FOR_CONTROL typedef struct _CMD_NODE { CMD_OPERATOR op; /* operator */ + BOOL do_echo; CMD_REDIRECTION *redirects; /* Redirections */ union { diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index ec61c7704ca..7a44698c86d 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -934,12 +934,6 @@ WCHAR *WCMD_skip_leading_spaces(WCHAR *string) return string; }
-static WCHAR *WCMD_strip_for_command_start(WCHAR *string) -{ - while (*string == L' ' || *string == L'\t' || *string == L'@') string++; - return string; -} - /*************************************************************************** * WCMD_keyword_ws_found * @@ -1656,13 +1650,14 @@ void node_dispose_tree(CMD_NODE *node) free(node); }
-static CMD_NODE *node_create_single(WCHAR *c) +static CMD_NODE *node_create_single(WCHAR *c, BOOL do_echo) { CMD_NODE *new = xalloc(sizeof(CMD_NODE));
new->op = CMD_SINGLE; new->command = c; new->redirects = NULL; + new->do_echo = do_echo;
return new; } @@ -1675,11 +1670,12 @@ static CMD_NODE *node_create_binary(CMD_OPERATOR op, CMD_NODE *l, CMD_NODE *r) new->left = l; new->right = r; new->redirects = NULL; + new->do_echo = TRUE; /* always TRUE */
return new; }
-static CMD_NODE *node_create_if(CMD_IF_CONDITION *cond, CMD_NODE *then_block, CMD_NODE *else_block) +static CMD_NODE *node_create_if(CMD_IF_CONDITION *cond, CMD_NODE *then_block, CMD_NODE *else_block, BOOL do_echo) { CMD_NODE *new = xalloc(sizeof(CMD_NODE));
@@ -1688,11 +1684,12 @@ static CMD_NODE *node_create_if(CMD_IF_CONDITION *cond, CMD_NODE *then_block, CM new->then_block = then_block; new->else_block = else_block; new->redirects = NULL; + new->do_echo = do_echo;
return new; }
-static CMD_NODE *node_create_for(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *do_block) +static CMD_NODE *node_create_for(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *do_block, BOOL do_echo) { CMD_NODE *new = xalloc(sizeof(CMD_NODE));
@@ -1700,17 +1697,19 @@ static CMD_NODE *node_create_for(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *do_block) new->for_ctrl = *for_ctrl; new->do_block = do_block; new->redirects = NULL; + new->do_echo = do_echo;
return new; }
-static CMD_NODE *node_create_block(CMD_NODE *block) +static CMD_NODE *node_create_block(CMD_NODE *block, BOOL do_echo) { CMD_NODE *new = xalloc(sizeof(CMD_NODE));
new->op = CMD_BLOCK; new->block = block; new->redirects = NULL; + new->do_echo = do_echo;
return new; } @@ -2678,7 +2677,7 @@ struct node_builder { enum builder_token { - TKN_EOF, TKN_EOL, TKN_REDIRECTION, TKN_FOR, TKN_IN, TKN_DO, TKN_IF, TKN_ELSE, + TKN_EOF, TKN_EOL, TKN_REDIRECTION, TKN_NOECHO, TKN_FOR, TKN_IN, TKN_DO, TKN_IF, TKN_ELSE, TKN_OPENPAR, TKN_CLOSEPAR, TKN_AMP, TKN_BARBAR, TKN_AMPAMP, TKN_BAR, TKN_COMMAND, } token; union token_parameter parameter; @@ -2689,7 +2688,7 @@ struct node_builder
static const char* debugstr_token(enum builder_token tkn, union token_parameter tkn_pmt) { - static const char *tokens[] = {"EOF", "EOL", "REDIR", "FOR", "IN", "DO", "IF", "ELSE", + static const char *tokens[] = {"EOF", "EOL", "REDIR", "NOECHO", "FOR", "IN", "DO", "IF", "ELSE", "(", ")", "&", "||", "&&", "|", "CMD"};
if (tkn >= ARRAY_SIZE(tokens)) return "<<<>>>"; @@ -2802,24 +2801,27 @@ static BOOL node_builder_parse(struct node_builder *builder, unsigned precedence CMD_FOR_CONTROL *for_ctrl = NULL; union token_parameter pmt; enum builder_token tkn; - BOOL done; + BOOL done, do_echo = TRUE;
#define ERROR_IF(x) if (x) {bogus_line = __LINE__; goto error_handling;} do { tkn = node_builder_peek_next_token(builder, &pmt); - done = FALSE; - TRACE("\t%u/%u) %s\n", builder->pos, builder->num, debugstr_token(tkn, pmt)); + switch (tkn) { - case TKN_EOF: - /* always an error to read past end of tokens */ + case TKN_EOF: /* always an error to read past end of tokens */ ERROR_IF(TRUE); break; case TKN_EOL: done = TRUE; break; + case TKN_NOECHO: /* should have already been handled */ + ERROR_IF(left); + node_builder_consume(builder); + do_echo = FALSE; + break; case TKN_OPENPAR: ERROR_IF(left); node_builder_consume(builder); @@ -2837,7 +2839,7 @@ static BOOL node_builder_parse(struct node_builder *builder, unsigned precedence left = node_create_binary(CMD_CONCAT, left, right); } node_builder_consume(builder); - left = node_create_block(left); + left = node_create_block(left, do_echo); /* if we had redirection before '(', add them up front */ if (redir) { @@ -2904,10 +2906,11 @@ static BOOL node_builder_parse(struct node_builder *builder, unsigned precedence break; case TKN_COMMAND: ERROR_IF(left); - left = node_create_single(pmt.command); + left = node_create_single(pmt.command, do_echo); node_builder_consume(builder); left->redirects = redir; redir = NULL; + do_echo = TRUE; break; case TKN_IF: ERROR_IF(left); @@ -2925,7 +2928,7 @@ static BOOL node_builder_parse(struct node_builder *builder, unsigned precedence { node_builder_consume(builder); free(pmt.command); - left = node_create_single(command_create(L"help if", 7)); + left = node_create_single(command_create(L"help if", 7), do_echo); break; } ERROR_IF(!if_condition_parse(pmt.command, &end, &cond)); @@ -2949,7 +2952,7 @@ static BOOL node_builder_parse(struct node_builder *builder, unsigned precedence } else else_block = NULL; - left = node_create_if(&cond, then_block, else_block); + left = node_create_if(&cond, then_block, else_block, do_echo); } break; case TKN_FOR: @@ -2965,7 +2968,7 @@ static BOOL node_builder_parse(struct node_builder *builder, unsigned precedence { node_builder_consume(builder); free(pmt.command); - left = node_create_single(command_create(L"help for", 8)); + left = node_create_single(command_create(L"help for", 8), do_echo); break; } node_builder_consume(builder); @@ -2993,7 +2996,7 @@ static BOOL node_builder_parse(struct node_builder *builder, unsigned precedence } while (tkn != TKN_CLOSEPAR); ERROR_IF(!node_builder_expect_token(builder, TKN_DO)); ERROR_IF(!node_builder_parse(builder, 0, &do_block)); - left = node_create_for(for_ctrl, do_block); + left = node_create_for(for_ctrl, do_block, do_echo); for_ctrl = NULL; } break; @@ -3521,6 +3524,7 @@ static BOOL rebuild_append_command(struct command_rebuild *rb, const CMD_NODE *n { BOOL ret;
+ if (!node->do_echo && !rebuild_append(rb, L"@")) return FALSE; switch (node->op) { case CMD_SINGLE: @@ -3567,7 +3571,10 @@ static BOOL lexer_can_accept_do(const struct node_builder *builder)
static BOOL lexer_at_command_start(const struct node_builder *builder) { - switch (node_builder_top(builder, 0)) + int idx = 0; + if (node_builder_top(builder, 0) == TKN_NOECHO) idx++; + + switch (node_builder_top(builder, idx)) { case TKN_EOF: case TKN_EOL: @@ -3577,12 +3584,25 @@ static BOOL lexer_at_command_start(const struct node_builder *builder) case TKN_AMPAMP: case TKN_BAR: case TKN_BARBAR: return TRUE; - case TKN_OPENPAR: return node_builder_top(builder, 1) != TKN_IN; - case TKN_COMMAND: return node_builder_top(builder, 1) == TKN_IF; + case TKN_OPENPAR: return node_builder_top(builder, idx + 1) != TKN_IN; + case TKN_COMMAND: return node_builder_top(builder, idx + 1) == TKN_IF; default: return FALSE; } }
+static WCHAR *lexer_strip_for_command_start(struct node_builder *builder, WCHAR *string) +{ + BOOL do_echo = TRUE; + while (*string == L' ' || *string == L'\t' || *string == L'@') + { + if (*string == L'@') do_echo = FALSE; + string++; + } + if (!do_echo) node_builder_push_token(builder, TKN_NOECHO); + + return string; +} + static BOOL lexer_white_space_only(const WCHAR *string, int len) { int i; @@ -3637,7 +3657,7 @@ enum read_parse_line WCMD_ReadAndParseLine(CMD_NODE **output) curCopyTo = curString; curLen = &curStringLen;
- curPos = WCMD_strip_for_command_start(curPos); + curPos = lexer_strip_for_command_start(&builder, curPos); /* Parse every character on the line being processed */ for (;;) { /* Debugging AID: @@ -3669,7 +3689,8 @@ enum read_parse_line WCMD_ReadAndParseLine(CMD_NODE **output)
/* Certain commands need special handling */ if (curStringLen == 0 && curCopyTo == curString) { - if (lexer_at_command_start(&builder) && !*(curPos = WCMD_strip_for_command_start(curPos))) continue; + if (lexer_at_command_start(&builder) && !*(curPos = lexer_strip_for_command_start(&builder, curPos))) + continue; /* If command starts with 'rem ' or identifies a label, use whole line */ if (WCMD_keyword_ws_found(L"rem", curPos) || *curPos == L':') { size_t line_len = wcslen(curPos);
From: Eric Pouech epouech@codeweavers.com
As we now store the explicit blocks from input, there's no need to handle precedence in output.
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/wcmdmain.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-)
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 7a44698c86d..3068f7db9c1 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -3270,8 +3270,7 @@ struct command_rebuild
struct rebuild_flags { - unsigned precedence : 3, - depth; + unsigned depth; };
static BOOL rebuild_append(struct command_rebuild *rb, const WCHAR *toappend) @@ -3371,25 +3370,23 @@ static BOOL rebuild_command_binary(struct command_rebuild *rb, const CMD_NODE *n
switch (node->op) { - case CMD_PIPE: op_string = L"|"; new_rbflags.precedence = 4; break; - case CMD_CONCAT: op_string = L"&"; new_rbflags.precedence = 3; break; - case CMD_ONFAILURE: op_string = L"||"; new_rbflags.precedence = 2; break; - case CMD_ONSUCCESS: op_string = L"&&"; new_rbflags.precedence = 1; break; + case CMD_PIPE: op_string = L"|"; break; + case CMD_CONCAT: op_string = L"&"; break; + case CMD_ONFAILURE: op_string = L"||"; break; + case CMD_ONSUCCESS: op_string = L"&&"; break; default: return FALSE; }
- return ((new_rbflags.precedence >= rbflags.precedence) || rebuild_append(rb, L"(")) && - rebuild_append_command(rb, node->left, new_rbflags) && + return rebuild_append_command(rb, node->left, new_rbflags) && ((node->left->op == CMD_SINGLE && node->op == CMD_CONCAT) ? rebuild_append(rb, L" ") : TRUE) && rebuild_append(rb, op_string) && - rebuild_append_command(rb, node->right, new_rbflags) && - ((new_rbflags.precedence >= rbflags.precedence) || rebuild_append(rb, L")")); + rebuild_append_command(rb, node->right, new_rbflags); }
static BOOL rebuild_command_if(struct command_rebuild *rb, const CMD_NODE *node, struct rebuild_flags rbflags) { const WCHAR *unop = NULL, *binop = NULL; - struct rebuild_flags new_rbflags = {.precedence = 0, .depth = rbflags.depth + 1}; + struct rebuild_flags new_rbflags = {.depth = rbflags.depth + 1}; BOOL ret;
ret = rebuild_append(rb, L"if "); @@ -3450,7 +3447,7 @@ static const WCHAR *state_to_delim(int state) static BOOL rebuild_command_for(struct command_rebuild *rb, const CMD_NODE *node, struct rebuild_flags rbflags) { const CMD_FOR_CONTROL *for_ctrl = &node->for_ctrl; - struct rebuild_flags new_rflags = {.precedence = 0, .depth = rbflags.depth + 1}; + struct rebuild_flags new_rflags = {.depth = rbflags.depth + 1}; const WCHAR *opt = NULL; BOOL ret;
@@ -3544,7 +3541,7 @@ static BOOL rebuild_append_command(struct command_rebuild *rb, const CMD_NODE *n break; case CMD_BLOCK: { - struct rebuild_flags new_rbflags = {.precedence = 0, .depth = rbflags.depth = 1}; + struct rebuild_flags new_rbflags = {.depth = rbflags.depth = 1}; ret = rebuild_append(rb, L"( ") && rebuild_append_command(rb, node->block, new_rbflags) && rebuild_append(rb, L" ) ");
From: Eric Pouech epouech@codeweavers.com
When in batch mode, echo of command is actually done when command is executed. This can be seen eg. in FOR loops, where the DO block is echo:ed each time the block is executed.
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/test_builtins.cmd.exp | 28 ++--- programs/cmd/wcmdmain.c | 126 +++++++++++++---------- 2 files changed, 88 insertions(+), 66 deletions(-)
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 6979c642085..197c4b88ef1 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -91,23 +91,23 @@ word
@todo_wine@@pwd@>echo a 1>nul@space@
-@todo_wine@@pwd@>if 1 == 2 then @echo a@space@ +@pwd@>if 1 == 2 then @echo a@space@
@todo_wine@@pwd@>IF 1 == 2 ThEn @EchO a@space@ -@todo_wine@ -@todo_wine@@pwd@>echo 1@space@ -@todo_wine@1 -@todo_wine@ -@todo_wine@@pwd@>echo 2@space@ -@todo_wine@2 -@todo_wine@--- + +@pwd@>echo 1@space@ +1 + +@pwd@>echo 2@space@ +2 +--- @todo_wine@AA@\x08@BB -@todo_wine@--- @ with chains and brackets +--- @ with chains and brackets
@todo_wine@@pwd@>(echo the @ character chains until && ) && echo and can hide brackets || () ||@space@ -@todo_wine@the @ character chains until -@todo_wine@we leave the current depth -@todo_wine@and can hide brackets +the @ character chains until +we leave the current depth +and can hide brackets @todo_wine@--- foo1
@@ -155,10 +155,10 @@ if 1==1 @echo bar if 1==1 echo foo2 if 1==1 @echo bar2
-@todo_wine@@pwd@>if 1 == 1 echo foo@space@ +@pwd@>if 1 == 1 echo foo@space@ foo
-@todo_wine@@pwd@>if 1 == 1@space@ +@pwd@>if 1 == 1@space@ bar foo2 bar2 diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 3068f7db9c1..c142ce2abd7 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -3229,35 +3229,6 @@ static WCHAR *fetch_next_line(BOOL first_line, WCHAR* buffer) handleExpansion(buffer, FALSE);
buffer = WCMD_skip_leading_spaces(buffer); - /* Show prompt before batch line IF echo is on and in batch program */ - if (WCMD_is_in_context(NULL) && echo_mode && *buffer && *buffer != L'@' && *buffer != L':') - { - if (first_line) - { - const size_t len = wcslen(L"echo."); - size_t curr_size = wcslen(buffer); - size_t min_len = curr_size < len ? curr_size : len; - WCMD_output_asis(L"\r\n"); - WCMD_show_prompt(); - WCMD_output_asis(buffer); - /* I don't know why Windows puts a space here but it does */ - /* Except for lines starting with 'echo.', 'echo:' or 'echo/'. Ask MS why */ - if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, - buffer, min_len, L"echo.", len) != CSTR_EQUAL - && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, - buffer, min_len, L"echo:", len) != CSTR_EQUAL - && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, - buffer, min_len, L"echo/", len) != CSTR_EQUAL) - { - WCMD_output_asis(L" "); - } - } - else - WCMD_output_asis(buffer); - - WCMD_output_asis(L"\r\n"); - } - return buffer; }
@@ -3270,7 +3241,8 @@ struct command_rebuild
struct rebuild_flags { - unsigned depth; + unsigned in_echo : 1, + depth; };
static BOOL rebuild_append(struct command_rebuild *rb, const WCHAR *toappend) @@ -3363,10 +3335,26 @@ static BOOL rebuild_append_all_redirections(struct command_rebuild *rb, const CM
static BOOL rebuild_append_command(struct command_rebuild *rb, const CMD_NODE *node, struct rebuild_flags rbflags);
+static BOOL rebuild_shall_echo(const CMD_NODE *node) +{ + if (!node->do_echo) return FALSE; + switch (node->op) + { + case CMD_PIPE: + case CMD_CONCAT: + case CMD_ONFAILURE: + case CMD_ONSUCCESS: + return rebuild_shall_echo(node->left) && rebuild_shall_echo(node->right); + default: + return TRUE; + } +} + static BOOL rebuild_command_binary(struct command_rebuild *rb, const CMD_NODE *node, struct rebuild_flags rbflags) { const WCHAR *op_string; - struct rebuild_flags new_rbflags = {.depth = rbflags.depth + 1}; + struct rebuild_flags new_rbflags = {.depth = rbflags.depth + 1, .in_echo = rbflags.in_echo}; + BOOL ret;
switch (node->op) { @@ -3377,16 +3365,20 @@ static BOOL rebuild_command_binary(struct command_rebuild *rb, const CMD_NODE *n default: return FALSE; }
- return rebuild_append_command(rb, node->left, new_rbflags) && - ((node->left->op == CMD_SINGLE && node->op == CMD_CONCAT) ? rebuild_append(rb, L" ") : TRUE) && - rebuild_append(rb, op_string) && - rebuild_append_command(rb, node->right, new_rbflags); + ret = rebuild_append_command(rb, node->left, new_rbflags) && + ((node->left->op == CMD_SINGLE && node->op == CMD_CONCAT) ? rebuild_append(rb, L" ") : TRUE); + if (!rbflags.in_echo || rebuild_shall_echo(node->left)) + { + ret = ret && rebuild_append(rb, op_string); + ret = ret && rebuild_append_command(rb, node->right, new_rbflags); + } + return ret; }
static BOOL rebuild_command_if(struct command_rebuild *rb, const CMD_NODE *node, struct rebuild_flags rbflags) { const WCHAR *unop = NULL, *binop = NULL; - struct rebuild_flags new_rbflags = {.depth = rbflags.depth + 1}; + struct rebuild_flags new_rbflags = {.depth = rbflags.depth + 1, .in_echo = rbflags.in_echo}; BOOL ret;
ret = rebuild_append(rb, L"if "); @@ -3447,7 +3439,7 @@ static const WCHAR *state_to_delim(int state) static BOOL rebuild_command_for(struct command_rebuild *rb, const CMD_NODE *node, struct rebuild_flags rbflags) { const CMD_FOR_CONTROL *for_ctrl = &node->for_ctrl; - struct rebuild_flags new_rflags = {.depth = rbflags.depth + 1}; + struct rebuild_flags new_rflags = {.depth = rbflags.depth + 1, .in_echo = rbflags.in_echo}; const WCHAR *opt = NULL; BOOL ret;
@@ -3521,11 +3513,22 @@ static BOOL rebuild_append_command(struct command_rebuild *rb, const CMD_NODE *n { BOOL ret;
- if (!node->do_echo && !rebuild_append(rb, L"@")) return FALSE; + if (!node->do_echo) + { + if (rbflags.in_echo) return TRUE; + if (!rebuild_append(rb, L"@")) return FALSE; + } switch (node->op) { case CMD_SINGLE: ret = rebuild_expand_and_append(rb, node->command, rbflags.depth == 0); + /* I don't know why Windows puts a space here but it does */ + /* Except for lines starting with 'echo.', 'echo:' or 'echo/'. Ask MS why */ + if (rbflags.in_echo && + (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, node->command, 4, L"echo", 4) != CSTR_EQUAL || + node->command[4] == L'\0' || + wcschr(L".:/", node->command[4]) == NULL)) + rebuild_append(rb, L" "); break; case CMD_PIPE: case CMD_CONCAT: @@ -3541,7 +3544,7 @@ static BOOL rebuild_append_command(struct command_rebuild *rb, const CMD_NODE *n break; case CMD_BLOCK: { - struct rebuild_flags new_rbflags = {.depth = rbflags.depth = 1}; + struct rebuild_flags new_rbflags = {.depth = rbflags.depth = 1, .in_echo = rbflags.in_echo}; ret = rebuild_append(rb, L"( ") && rebuild_append_command(rb, node->block, new_rbflags) && rebuild_append(rb, L" ) "); @@ -4012,6 +4015,8 @@ static BOOL if_condition_evaluate(CMD_IF_CONDITION *cond, int *test) return TRUE; }
+static RETURN_CODE node_execute_with_echo(CMD_NODE *node, BOOL with_echo); + struct for_loop_variables { unsigned char table[32]; @@ -4142,7 +4147,7 @@ static RETURN_CODE for_loop_fileset_parse_line(CMD_NODE *node, unsigned varidx, /* Execute the body of the for loop with these values */ if (forloopcontext->variable[varidx] && forloopcontext->variable[varidx][0] != forf_eol) { - return_code = node_execute(node); + return_code = node_execute_with_echo(node, echo_mode); } else { @@ -4355,14 +4360,14 @@ static RETURN_CODE for_control_execute_set(CMD_FOR_CONTROL *for_ctrl, const WCHA if (insert_pos + wcslen(fd.cFileName) + 1 >= ARRAY_SIZE(buffer)) continue; wcscpy(&buffer[insert_pos], fd.cFileName); WCMD_set_for_loop_variable(for_ctrl->variable_index, buffer); - return_code = node_execute(node); + return_code = node_execute_with_echo(node, echo_mode); } while (!WCMD_is_break(return_code) && FindNextFileW(hff, &fd) != 0); FindClose(hff); } else { WCMD_set_for_loop_variable(for_ctrl->variable_index, buffer); - return_code = node_execute(node); + return_code = node_execute_with_echo(node, echo_mode); } } return return_code; @@ -4437,7 +4442,7 @@ static RETURN_CODE for_control_execute_numbers(CMD_FOR_CONTROL *for_ctrl, CMD_NO swprintf(tmp, ARRAY_SIZE(tmp), L"%d", var); WCMD_set_for_loop_variable(for_ctrl->variable_index, tmp); TRACE("Processing FOR number %s\n", wine_dbgstr_w(tmp)); - return_code = node_execute(node); + return_code = node_execute_with_echo(node, echo_mode); } return return_code; } @@ -4596,13 +4601,26 @@ static RETURN_CODE handle_pipe_command(CMD_NODE *node) return errorlevel = return_code; }
-RETURN_CODE node_execute(CMD_NODE *node) +static RETURN_CODE node_execute_with_echo(CMD_NODE *node, BOOL with_echo) { HANDLE saved[3]; RETURN_CODE return_code; int test;
if (!node) return NO_ERROR; + if (with_echo && node->do_echo && (node->op != CMD_SINGLE || node->command[0] != L':')) + { + WCHAR buffer[MAXSTRING]; + struct command_rebuild rb = {buffer, ARRAY_SIZE(buffer), 0}; + struct rebuild_flags rbflags = {.depth = 0, .in_echo = 1}; + if (rebuild_append_command(&rb, node, rbflags)) + { + WCMD_output_asis(L"\r\n"); + WCMD_show_prompt(); + WCMD_output_asis(buffer); + WCMD_output_asis(L"\r\n"); + } + } if (!push_std_redirections(node->redirects, saved)) { WCMD_print_error(); @@ -4616,22 +4634,22 @@ RETURN_CODE node_execute(CMD_NODE *node) else return_code = NO_ERROR; break; case CMD_CONCAT: - return_code = node_execute(node->left); + return_code = node_execute_with_echo(node->left, FALSE); if (!WCMD_is_break(return_code)) - return_code = node_execute(node->right); + return_code = node_execute_with_echo(node->right, FALSE); break; case CMD_ONSUCCESS: - return_code = node_execute(node->left); + return_code = node_execute_with_echo(node->left, FALSE); if (return_code == NO_ERROR) - return_code = node_execute(node->right); + return_code = node_execute_with_echo(node->right, FALSE); break; case CMD_ONFAILURE: - return_code = node_execute(node->left); + return_code = node_execute_with_echo(node->left, FALSE); if (return_code != NO_ERROR && !WCMD_is_break(return_code)) { /* that's needed for commands (POPD, RMDIR) that don't set errorlevel in case of failure. */ errorlevel = return_code; - return_code = node_execute(node->right); + return_code = node_execute_with_echo(node->right, FALSE); } break; case CMD_PIPE: @@ -4639,7 +4657,7 @@ RETURN_CODE node_execute(CMD_NODE *node) break; case CMD_IF: if (if_condition_evaluate(&node->condition, &test)) - return_code = node_execute(test ? node->then_block : node->else_block); + return_code = node_execute_with_echo(test ? node->then_block : node->else_block, FALSE); else return_code = ERROR_INVALID_FUNCTION; break; @@ -4647,7 +4665,7 @@ RETURN_CODE node_execute(CMD_NODE *node) return_code = for_control_execute(&node->for_ctrl, node->do_block); break; case CMD_BLOCK: - return_code = node_execute(node->block); + return_code = node_execute_with_echo(node->block, FALSE); break; default: FIXME("Unexpected operator %u\n", node->op); @@ -4658,6 +4676,10 @@ RETURN_CODE node_execute(CMD_NODE *node) return return_code; }
+RETURN_CODE node_execute(CMD_NODE *node) +{ + return node_execute_with_echo(node, echo_mode && WCMD_is_in_context(NULL)); +}
RETURN_CODE WCMD_ctrlc_status(void) {
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/test_builtins.cmd.exp | 2 +- programs/cmd/wcmdmain.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 197c4b88ef1..06b892ba865 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -101,7 +101,7 @@ word @pwd@>echo 2@space@ 2 --- -@todo_wine@AA@\x08@BB +AA@\x08@BB --- @ with chains and brackets
@todo_wine@@pwd@>(echo the @ character chains until && ) && echo and can hide brackets || () ||@space@ diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index c142ce2abd7..088c470922f 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -830,6 +830,8 @@ static void WCMD_show_prompt(void) *q++ = '>'; break; case 'H': + *q++ = '\b'; + *q++ = ' '; *q++ = '\b'; break; case 'L':
From: Eric Pouech epouech@codeweavers.com
Wine-Bug-Id: https://bugs.winehq.org/show_bug.cgi?id=38289
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/test_builtins.cmd.exp | 2 +- programs/cmd/wcmdmain.c | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 06b892ba865..ed8c3f11e2f 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -988,7 +988,7 @@ foo not empty --- nested expansion foo -@todo_wine@foo +foo --- using /V cmd flag foo foo@or_broken@!WINE_FOO! diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 088c470922f..1eb40d46644 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -981,7 +981,7 @@ static inline int read_int_in_range(const WCHAR *from, WCHAR **after, int low, i /************************************************************************* * WCMD_expand_envvar * - * Expands environment variables, allowing for WCHARacter substitution + * Expands environment variables, allowing for character substitution */ static WCHAR *WCMD_expand_envvar(WCHAR *start) { @@ -1223,6 +1223,18 @@ static void handleExpansion(WCHAR *cmd, BOOL atExecute) { p = WCMD_strsubstW(p, p + 2, forloopcontext->variable[p[1]], -1); } else if (!atExecute || startchar == L'!') { BOOL first = p == cmd; + /* env var delimited by % have been expanded at parse time, but there could still be + * loop variables nested inside env var delimited by ! + */ + if (startchar == L'!') + { + WCHAR *ptr; + for (ptr = p + 1; *ptr && *ptr != startchar; ptr++) + if (*ptr == L'%' && for_var_is_valid(ptr[1]) && forloopcontext->variable[ptr[1]]) { + /* Replace the 2 characters, % and for variable character */ + ptr = WCMD_strsubstW(ptr, ptr + 2, forloopcontext->variable[ptr[1]], -1); + } + } p = WCMD_expand_envvar(p); /* FIXME: maybe this more likely calls for a specific handling of first arg? */ if (WCMD_is_in_context(NULL) && startchar == L'!' && first)