[PATCH 0/7] MR9554: Cmd engine rewrite (part XXXXI)
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. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9554
From: Eric Pouech <epouech(a)codeweavers.com> Signed-off-by: Eric Pouech <epouech(a)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 -(a)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: -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9554
From: Eric Pouech <epouech(a)codeweavers.com> Signed-off-by: Eric Pouech <epouech(a)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(a)word echo @tab(a)word echo(a)tab@@tab(a)word echo @tab@ on @space@ +> nul echo a +if(a)tab@1 == 2 then @echo a +(a)rem native stores the keyword (and preserve the case) :-( +IF(a)tab@1 == 2 ThEn @EchO a +(a)rem echo is done at execution time +(a)for %%a in (1 2) do echo %%a +(a)echo --- +(a)rem this convoluted code captures ^H inside BS env variable +(a)for /f %%a in ('"prompt $H&for %%b in (1) do rem"') do @set "BS=%%a" +(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 --- - +(a)set V=@ +%V%echo foo1 +> nul echo a && @echo foo2 +(a)echo --- @echo off echo off(a)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(a)word @pwd@>echo @tab@ on @space@@space@ ---- @ with chains and brackets + +(a)todo_wine@@pwd@>echo a 1>nul(a)space@ + +(a)todo_wine@@pwd@>if 1 == 2 then @echo a(a)space@ + +(a)todo_wine@@pwd@>IF 1 == 2 ThEn @EchO a(a)space@ +(a)todo_wine@ +(a)todo_wine@@pwd@>echo 1(a)space@ +(a)todo_wine@1 +(a)todo_wine@ +(a)todo_wine@@pwd@>echo 2(a)space@ +(a)todo_wine@2 +(a)todo_wine@--- +(a)todo_wine@AA@\x08(a)BB +(a)todo_wine@--- @ with chains and brackets @todo_wine@@pwd@>(echo the @ character chains until && ) && echo and can hide brackets || () ||@space@ @todo_wine(a)the @ character chains until @todo_wine(a)we leave the current depth @todo_wine(a)and can hide brackets @todo_wine(a)--- +foo1 + +(a)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 +(a)todo_wine@foo --- using /V cmd flag foo foo(a)or_broken@!WINE_FOO! -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9554
From: Eric Pouech <epouech(a)codeweavers.com> Introducing: - new lexer token for the '@' input character, Signed-off-by: Eric Pouech <epouech(a)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); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9554
From: Eric Pouech <epouech(a)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(a)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" ) "); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9554
From: Eric Pouech <epouech(a)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(a)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(a)space@ -(a)todo_wine@@pwd@>if 1 == 2 then @echo a(a)space@ +(a)pwd@>if 1 == 2 then @echo a(a)space@ @todo_wine@@pwd@>IF 1 == 2 ThEn @EchO a(a)space@ -(a)todo_wine@ -(a)todo_wine@@pwd@>echo 1(a)space@ -(a)todo_wine@1 -(a)todo_wine@ -(a)todo_wine@@pwd@>echo 2(a)space@ -(a)todo_wine@2 -(a)todo_wine@--- + +(a)pwd@>echo 1(a)space@ +1 + +(a)pwd@>echo 2(a)space@ +2 +--- @todo_wine(a)AA@\x08(a)BB -(a)todo_wine@--- @ with chains and brackets +--- @ with chains and brackets @todo_wine@@pwd@>(echo the @ character chains until && ) && echo and can hide brackets || () ||@space@ -(a)todo_wine@the @ character chains until -(a)todo_wine@we leave the current depth -(a)todo_wine@and can hide brackets +the @ character chains until +we leave the current depth +and can hide brackets @todo_wine(a)--- foo1 @@ -155,10 +155,10 @@ if 1==1 @echo bar if 1==1 echo foo2 if 1==1 @echo bar2 -(a)todo_wine@@pwd@>if 1 == 1 echo foo(a)space@ +(a)pwd@>if 1 == 1 echo foo(a)space@ foo -(a)todo_wine@@pwd@>if 1 == 1(a)space@ +(a)pwd@>if 1 == 1(a)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) { -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9554
From: Eric Pouech <epouech(a)codeweavers.com> Signed-off-by: Eric Pouech <epouech(a)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(a)space@ 2 --- -(a)todo_wine@AA@\x08(a)BB +AA@\x08(a)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': -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9554
From: Eric Pouech <epouech(a)codeweavers.com> Wine-Bug-Id: https://bugs.winehq.org/show_bug.cgi?id=38289 Signed-off-by: Eric Pouech <epouech(a)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 -(a)todo_wine@foo +foo --- using /V cmd flag foo foo(a)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) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9554
participants (2)
-
Eric Pouech -
eric pouech (@epo)