This is the part VI of the cmd engine rewrite.
It starts splitting FOR loop parsing from its execution. This is done for numbers (/L) and filesets (/F) FOR loops. It also extends support for delay expansion of various parts of these loops.
Next MR will tackle the tree oriented FOR loop handling.
From: Eric Pouech epouech@codeweavers.com
Introducing CMD_FOR_CONTROL structure to store parsing output for consumption in execution.
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/builtins.c | 40 +++++++-- programs/cmd/wcmd.h | 20 +++++ programs/cmd/wcmdmain.c | 178 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 7 deletions(-)
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index b28638ce70d..86248231363 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -1546,8 +1546,8 @@ void WCMD_echo (const WCHAR *args) * first command to be executed may not be at the front of the * commands->thiscommand string (eg. it may point after a DO or ELSE) */ -static void WCMD_part_execute(CMD_NODE **cmdList, const WCHAR *firstcmd, - BOOL isIF, BOOL executecmds) +void WCMD_part_execute(CMD_NODE **cmdList, const WCHAR *firstcmd, + BOOL isIF, BOOL executecmds) { CMD_NODE *curPosition = *cmdList; int myDepth = CMD_node_get_depth(*cmdList); @@ -1850,11 +1850,11 @@ static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) * are recursively passed. If any have duplicates, then the * token should * not be honoured. */ -static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr, - int *totalfound, BOOL *doall, - BOOL *duplicates) +int WCMD_for_nexttoken(int lasttoken, const WCHAR *tokenstr, + int *totalfound, BOOL *doall, + BOOL *duplicates) { - WCHAR *pos = tokenstr; + const WCHAR *pos = tokenstr; int nexttoken = -1;
if (totalfound) *totalfound = 0; @@ -2136,7 +2136,7 @@ static FILE *WCMD_forf_getinput(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) * */
-void WCMD_for (WCHAR *p, CMD_NODE **cmdList) { +static void WCMD_for_OLD (WCHAR *p, CMD_NODE **cmdList) {
WIN32_FIND_DATAW fd; HANDLE hff; @@ -2541,6 +2541,32 @@ void WCMD_for (WCHAR *p, CMD_NODE **cmdList) { if (cmdEnd && CMD_node_get_command(cmdEnd)->command == NULL) *cmdList = CMD_node_next(cmdEnd); }
+void WCMD_for(WCHAR *p, CMD_NODE **cmdList) +{ + CMD_FOR_CONTROL *for_ctrl; + + for_ctrl = for_control_parse(p); + if (!for_ctrl) + { + /* temporary code: use OLD code for non migrated FOR constructs */ + WCMD_for_OLD(p, cmdList); + return; + } + + for (*cmdList = CMD_node_next(*cmdList); /* swallow options */ + *cmdList && CMD_node_get_command(*cmdList)->command != NULL; + *cmdList = CMD_node_next(*cmdList)) + { + for_control_append_set(for_ctrl, CMD_node_get_command(*cmdList)->command); + } + + /* step over terminating NULL CMD_NODE of set */ + *cmdList = CMD_node_next(*cmdList); + + for_control_execute(for_ctrl, cmdList); + for_control_dispose(for_ctrl); +} + /************************************************************************** * WCMD_give_help * diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index dde5d1e12ec..3eb9731c45d 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -83,6 +83,14 @@ typedef struct _CMD_IF_CONDITION }; } CMD_IF_CONDITION;
+typedef struct _CMD_FOR_CONTROL +{ + enum for_control_operator {CMD_FOR_NUMBERS /* /L */} operator; + unsigned flags; /* |-ed CMD_FOR_FLAG_* */ + int variable_index; + const WCHAR *set; +} CMD_FOR_CONTROL; + typedef struct _CMD_COMMAND { WCHAR *command; /* Command string to execute */ @@ -129,6 +137,18 @@ void if_condition_dispose(CMD_IF_CONDITION *); BOOL if_condition_evaluate(CMD_IF_CONDITION *cond, int *test); const char *debugstr_if_condition(const CMD_IF_CONDITION *cond);
+void for_control_create(enum for_control_operator for_op, unsigned flags, const WCHAR *options, int var_idx, CMD_FOR_CONTROL *for_ctrl); +CMD_FOR_CONTROL *for_control_parse(WCHAR *opts_var); +void for_control_append_set(CMD_FOR_CONTROL *for_ctrl, const WCHAR *string); +void for_control_dump(const CMD_FOR_CONTROL *for_ctrl); +void for_control_dispose(CMD_FOR_CONTROL *for_ctrl); +void for_control_execute(CMD_FOR_CONTROL *for_ctrl, CMD_NODE **cmdList); +int WCMD_for_nexttoken(int lasttoken, const WCHAR *tokenstr, + int *totalfound, BOOL *doall, + BOOL *duplicates); +void WCMD_part_execute(CMD_NODE **cmdList, const WCHAR *firstcmd, + BOOL isIF, BOOL executecmds); + void WCMD_assoc (const WCHAR *, BOOL); void WCMD_batch (WCHAR *, WCHAR *, BOOL, WCHAR *, HANDLE); void WCMD_call (WCHAR *command); diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index da9d93f4dde..e86ad8042f9 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -991,6 +991,65 @@ static void command_dispose(CMD_COMMAND *cmd) } }
+void for_control_dispose(CMD_FOR_CONTROL *for_ctrl) +{ + free((void*)for_ctrl->set); + switch (for_ctrl->operator) + { + default: + break; + } +} + +const char *debugstr_for_control(const CMD_FOR_CONTROL *for_ctrl) +{ + static const char* for_ctrl_strings[] = {"numbers"}; + const char *flags, *options; + + if (for_ctrl->operator >= ARRAY_SIZE(for_ctrl_strings)) + { + FIXME("Unexpected operator\n"); + return wine_dbg_sprintf("<<%u>>", for_ctrl->operator); + } + + flags = ""; + switch (for_ctrl->operator) + { + default: + options = ""; + break; + } + return wine_dbg_sprintf("[FOR] %s %s%s%%%c (%ls)", + for_ctrl_strings[for_ctrl->operator], flags, options, + for_var_index_to_char(for_ctrl->variable_index), for_ctrl->set); +} + +void for_control_create(enum for_control_operator for_op, unsigned flags, const WCHAR *options, int var_idx, CMD_FOR_CONTROL *for_ctrl) +{ + for_ctrl->operator = for_op; + for_ctrl->flags = flags; + for_ctrl->variable_index = var_idx; + for_ctrl->set = NULL; + switch (for_ctrl->operator) + { + default: + break; + } +} + +void for_control_append_set(CMD_FOR_CONTROL *for_ctrl, const WCHAR *set) +{ + if (for_ctrl->set) + { + for_ctrl->set = xrealloc((void*)for_ctrl->set, + (wcslen(for_ctrl->set) + 1 + wcslen(set) + 1) * sizeof(WCHAR)); + wcscat((WCHAR*)for_ctrl->set, L" "); + wcscat((WCHAR*)for_ctrl->set, set); + } + else + for_ctrl->set = xstrdupW(set); +} + /*************************************************************************** * node_dispose_tree * @@ -2093,6 +2152,70 @@ static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex) return FALSE; }
+CMD_FOR_CONTROL *for_control_parse(WCHAR *opts_var) +{ + CMD_FOR_CONTROL *for_ctrl; + enum for_control_operator for_op; + WCHAR mode = L' ', option; + WCHAR options[MAXSTRING]; + WCHAR *arg; + unsigned flags = 0; + int arg_index; + int var_idx; + + options[0] = L'\0'; + /* native allows two options only in the /D /R case, a repetition of the option + * and prints an error otherwise + */ + for (arg_index = 0; ; arg_index++) + { + arg = WCMD_parameter(opts_var, arg_index, NULL, FALSE, FALSE); + + if (!arg || *arg != L'/') break; + option = towupper(arg[1]); + if (mode != L' ' && (mode != L'D' || option != 'R') && mode != option) + break; + switch (option) + { + case L'R': + if (mode == L'D') + { + mode = L'X'; + break; + } + /* fall thru */ + case L'D': + case L'L': + case L'F': + mode = option; + break; + default: + /* error unexpected 'arg' at this time */ + WARN("for qualifier '%c' unhandled\n", *arg); + goto syntax_error; + } + } + switch (mode) + { + case L'L': + for_op = CMD_FOR_NUMBERS; + break; + default: + return NULL; + } + + /* Ensure line continues with variable */ + arg = WCMD_parameter(opts_var, arg_index++, NULL, FALSE, FALSE); + if (!arg || *arg != L'%' || (var_idx = for_var_char_to_index(arg[1])) == -1) + goto syntax_error; /* FIXME native prints the offending token "%<whatever>" was unexpected at this time */ + for_ctrl = xalloc(sizeof(*for_ctrl)); + for_control_create(for_op, flags, options, var_idx, for_ctrl); + return for_ctrl; +syntax_error: + WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR)); + return NULL; +} + /*************************************************************************** * WCMD_ReadAndParseLine * @@ -2748,6 +2871,61 @@ void WCMD_set_for_loop_variable(int var_idx, const WCHAR *value) forloopcontext->variable[var_idx] = xstrdupW(value); }
+static CMD_NODE *for_control_execute_numbers(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *cmdList) +{ + WCHAR set[MAXSTRING]; + CMD_NODE *body = NULL; + int numbers[3] = {0, 0, 0}, var; + int i; + + wcscpy(set, for_ctrl->set); + + /* Note: native doesn't check the actual number of parameters, and set + * them by default to 0. + * so (-10 1) is interpreted as (-10 1 0) + * and (10) loops for ever !!! + */ + for (i = 0; i < ARRAY_SIZE(numbers); i++) + { + WCHAR *element = WCMD_parameter(set, i, NULL, FALSE, FALSE); + if (!element || !*element) break; + /* native doesn't no error handling */ + numbers[i] = wcstol(element, NULL, 0); + } + + for (var = numbers[0]; + (numbers[1] < 0) ? var >= numbers[2] : var <= numbers[2]; + var += numbers[1]) + { + WCHAR tmp[32]; + + body = cmdList; + 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)); + WCMD_part_execute(&body, CMD_node_get_command(cmdList)->command + 3, FALSE, TRUE); + } + return body; +} + +void for_control_execute(CMD_FOR_CONTROL *for_ctrl, CMD_NODE **cmdList) +{ + CMD_NODE *last; + WCMD_save_for_loop_context(FALSE); + + switch (for_ctrl->operator) + { + case CMD_FOR_NUMBERS: + last = for_control_execute_numbers(for_ctrl, *cmdList); + break; + default: + last = NULL; + break; + } + WCMD_restore_for_loop_context(); + *cmdList = last; +} + /*************************************************************************** * WCMD_process_commands *
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/test_builtins.cmd | 5 ----- programs/cmd/tests/test_builtins.cmd.exp | 22 +++++++++++----------- programs/cmd/wcmdmain.c | 1 + 3 files changed, 12 insertions(+), 16 deletions(-)
diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index 2ef2d495a63..c9951a66373 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -757,11 +757,6 @@ setlocal EnableDelayedExpansion set WINE_FOO=foo bar for %%i in ("!WINE_FOO!") do echo %%i for %%i in (!WINE_FOO!) do echo %%i -rem tests disabled for now... wine's cmd loops endlessly here -rem set WINE_FOO=4 4 4 -rem for /l %%i in (!WINE_FOO!) do echo %%i -rem set WINE_FOO=4 -rem for /l %%i in (1 2 !WINE_FOO!) do echo %%i setlocal DisableDelayedExpansion
echo --- in digit variables diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 1c3b0dc81cc..b5989b55f52 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -1018,17 +1018,17 @@ B D @todo_wine@inner argument {-x, y} --- nesting and delayed expansion a 1 2 -@todo_wine@1-A2 -@todo_wine@1-B1 -@todo_wine@2-A1 -@todo_wine@2-B2 -@todo_wine@b 1 3 -@todo_wine@1-A2 -@todo_wine@1-B1 -@todo_wine@2-A2 -@todo_wine@2-B2 -@todo_wine@3-A1 -@todo_wine@3-B2 +1-A2 +1-B1 +2-A1 +2-B2 +b 1 3 +1-A2 +1-B1 +2-A2 +2-B2 +3-A1 +3-B2 --- nesting if/for @todo_wine@"f" @todo_wine@"g" diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index e86ad8042f9..b50c159414e 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -2879,6 +2879,7 @@ static CMD_NODE *for_control_execute_numbers(CMD_FOR_CONTROL *for_ctrl, CMD_NODE int i;
wcscpy(set, for_ctrl->set); + handleExpansion(set, context != NULL, delayedsubst);
/* Note: native doesn't check the actual number of parameters, and set * them by default to 0.
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/test_builtins.cmd.exp | 2 +- programs/cmd/wcmd.h | 18 +- programs/cmd/wcmdmain.c | 337 ++++++++++++++++++++++- 3 files changed, 353 insertions(+), 4 deletions(-)
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index b5989b55f52..a7af684cb9a 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -1385,7 +1385,7 @@ h=%h i=b j=c k= l= m=%m n=%n o=%o@or_broken@h=%h i=b j=c k= l= m= n=%n o=%o h=%h i=b j=c k= l= m=%m n=%n o=%o@or_broken@h=%h i=b j=c k= l= m= n=%n o=%o 1:3.14,%B 2:3.14, -@todo_wine@4:3,14 +4:3,14 ------ parameter splitting :forFParameterSplittingFunc myparam1=myvalue1 myparam2=myparam2 mytest@space@@space@@space@ :forFParameterSplittingFunc myparam1=myvalue1 myparam2=myparam2 mytest@space@@space@@space@ diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index 3eb9731c45d..c1a95c6223c 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -85,10 +85,23 @@ typedef struct _CMD_IF_CONDITION
typedef struct _CMD_FOR_CONTROL { - enum for_control_operator {CMD_FOR_NUMBERS /* /L */} operator; + enum for_control_operator { + CMD_FOR_FILE_SET /* /F */, + CMD_FOR_NUMBERS /* /L */} operator; unsigned flags; /* |-ed CMD_FOR_FLAG_* */ int variable_index; const WCHAR *set; + union + { + struct /* for CMD_FOR_FILE_SET */ + { + WCHAR eol; + BOOL use_backq; + int num_lines_to_skip; + const WCHAR *delims; + const WCHAR *tokens; + }; + }; } CMD_FOR_CONTROL;
typedef struct _CMD_COMMAND @@ -138,6 +151,9 @@ BOOL if_condition_evaluate(CMD_IF_CONDITION *cond, int *test); const char *debugstr_if_condition(const CMD_IF_CONDITION *cond);
void for_control_create(enum for_control_operator for_op, unsigned flags, const WCHAR *options, int var_idx, CMD_FOR_CONTROL *for_ctrl); +void for_control_create_fileset(unsigned flags, int var_idx, WCHAR eol, int num_lines_to_skip, BOOL use_backq, + const WCHAR *delims, const WCHAR *tokens, + CMD_FOR_CONTROL *for_ctrl); CMD_FOR_CONTROL *for_control_parse(WCHAR *opts_var); void for_control_append_set(CMD_FOR_CONTROL *for_ctrl, const WCHAR *string); void for_control_dump(const CMD_FOR_CONTROL *for_ctrl); diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index b50c159414e..5f7d399b4e7 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -996,6 +996,10 @@ void for_control_dispose(CMD_FOR_CONTROL *for_ctrl) free((void*)for_ctrl->set); switch (for_ctrl->operator) { + case CMD_FOR_FILE_SET: + free((void*)for_ctrl->delims); + free((void*)for_ctrl->tokens); + break; default: break; } @@ -1003,7 +1007,7 @@ void for_control_dispose(CMD_FOR_CONTROL *for_ctrl)
const char *debugstr_for_control(const CMD_FOR_CONTROL *for_ctrl) { - static const char* for_ctrl_strings[] = {"numbers"}; + static const char* for_ctrl_strings[] = {"file", "numbers"}; const char *flags, *options;
if (for_ctrl->operator >= ARRAY_SIZE(for_ctrl_strings)) @@ -1015,6 +1019,15 @@ const char *debugstr_for_control(const CMD_FOR_CONTROL *for_ctrl) flags = ""; switch (for_ctrl->operator) { + case CMD_FOR_FILE_SET: + { + WCHAR eol_buf[4] = {L''', for_ctrl->eol, L''', L'\0'}; + const WCHAR *eol = for_ctrl->eol ? eol_buf : L"<nul>"; + options = wine_dbg_sprintf("eol=%ls skip=%d use_backq=%c delims=%s tokens=%s ", + eol, for_ctrl->num_lines_to_skip, for_ctrl->use_backq ? 'Y' : 'N', + wine_dbgstr_w(for_ctrl->delims), wine_dbgstr_w(for_ctrl->tokens)); + } + break; default: options = ""; break; @@ -1037,6 +1050,22 @@ void for_control_create(enum for_control_operator for_op, unsigned flags, const } }
+void for_control_create_fileset(unsigned flags, int var_idx, WCHAR eol, int num_lines_to_skip, BOOL use_backq, + const WCHAR *delims, const WCHAR *tokens, + CMD_FOR_CONTROL *for_ctrl) +{ + for_ctrl->operator = CMD_FOR_FILE_SET; + for_ctrl->flags = flags; + for_ctrl->variable_index = var_idx; + for_ctrl->set = NULL; + + for_ctrl->eol = eol; + for_ctrl->use_backq = use_backq; + for_ctrl->num_lines_to_skip = num_lines_to_skip; + for_ctrl->delims = delims; + for_ctrl->tokens = tokens; +} + void for_control_append_set(CMD_FOR_CONTROL *for_ctrl, const WCHAR *set) { if (for_ctrl->set) @@ -2152,6 +2181,19 @@ static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex) return FALSE; }
+static WCHAR *for_fileset_option_split(WCHAR *from, const WCHAR* key) +{ + size_t len = wcslen(key); + + if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, + from, len, key, len) != CSTR_EQUAL) + return NULL; + from += len; + if (len && key[len - 1] == L'=') + while (*from && *from != L' ' && *from != L'\t') from++; + return from; +} + CMD_FOR_CONTROL *for_control_parse(WCHAR *opts_var) { CMD_FOR_CONTROL *for_ctrl; @@ -2200,16 +2242,103 @@ CMD_FOR_CONTROL *for_control_parse(WCHAR *opts_var) case L'L': for_op = CMD_FOR_NUMBERS; break; + case L'F': + for_op = CMD_FOR_FILE_SET; + break; default: return NULL; }
+ if (mode == L'F') + { + /* Retrieve next parameter to see if is root/options (raw form required + * with for /f, or unquoted in for /r) + */ + arg = WCMD_parameter(opts_var, arg_index, NULL, for_op == CMD_FOR_FILE_SET, FALSE); + + /* Next parm is either qualifier, path/options or variable - + * only care about it if it is the path/options + */ + if (arg && *arg != L'/' && *arg != L'%') + { + arg_index++; + wcscpy(options, arg); + } + } + /* Ensure line continues with variable */ arg = WCMD_parameter(opts_var, arg_index++, NULL, FALSE, FALSE); if (!arg || *arg != L'%' || (var_idx = for_var_char_to_index(arg[1])) == -1) goto syntax_error; /* FIXME native prints the offending token "%<whatever>" was unexpected at this time */ for_ctrl = xalloc(sizeof(*for_ctrl)); - for_control_create(for_op, flags, options, var_idx, for_ctrl); + if (for_op == CMD_FOR_FILE_SET) + { + size_t len = wcslen(options); + WCHAR *p = options, *end; + WCHAR eol = L'\0'; + int num_lines_to_skip = 0; + BOOL use_backq = FALSE; + WCHAR *delims = NULL, *tokens = NULL; + /* strip enclosing double-quotes when present */ + if (len >= 2 && p[0] == L'"' && p[len - 1] == L'"') + { + p[len - 1] = L'\0'; + p++; + } + for ( ; *p; p = end) + { + p = WCMD_skip_leading_spaces(p); + /* Save End of line character (Ignore line if first token (based on delims) starts with it) */ + if ((end = for_fileset_option_split(p, L"eol="))) + { + /* assuming one char for eol marker */ + if (end != p + 5) goto syntax_error; + eol = p[4]; + } + /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */ + else if ((end = for_fileset_option_split(p, L"skip="))) + { + WCHAR *nextchar; + num_lines_to_skip = wcstoul(p + 5, &nextchar, 0); + if (end != nextchar) goto syntax_error; + } + /* Save if usebackq semantics are in effect */ + else if ((end = for_fileset_option_split(p, L"usebackq"))) + use_backq = TRUE; + /* Save the supplied delims */ + else if ((end = for_fileset_option_split(p, L"delims="))) + { + size_t copy_len; + + /* interpret space when last character of whole options string as part of delims= */ + if (end[0] && !end[1]) end++; + copy_len = end - (p + 7) /* delims= */; + delims = xalloc((copy_len + 1) * sizeof(WCHAR)); + memcpy(delims, p + 7, copy_len * sizeof(WCHAR)); + delims[copy_len] = L'\0'; + } + /* Save the tokens being requested */ + else if ((end = for_fileset_option_split(p, L"tokens="))) + { + size_t copy_len; + + copy_len = end - (p + 7) /* tokens= */; + tokens = xalloc((copy_len + 1) * sizeof(WCHAR)); + memcpy(tokens, p + 7, copy_len * sizeof(WCHAR)); + tokens[copy_len] = L'\0'; + } + else + { + WARN("FOR option not found %ls\n", p); + goto syntax_error; + } + } + for_control_create_fileset(flags, var_idx, eol, num_lines_to_skip, use_backq, + delims ? delims : xstrdupW(L" \t"), + tokens ? tokens : xstrdupW(L"1"), for_ctrl); + } + else + for_control_create(for_op, flags, options, var_idx, for_ctrl); return for_ctrl; syntax_error: WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR)); @@ -2833,6 +2962,97 @@ BOOL if_condition_evaluate(CMD_IF_CONDITION *cond, int *test) return TRUE; }
+static CMD_NODE *for_loop_fileset_parse_line(CMD_NODE *cmdList, int varidx, WCHAR *buffer, + WCHAR forf_eol, const WCHAR *forf_delims, const WCHAR *forf_tokens) +{ + WCHAR *parm; + int varoffset; + int nexttoken, lasttoken = -1; + BOOL starfound = FALSE; + BOOL thisduplicate = FALSE; + BOOL anyduplicates = FALSE; + int totalfound; + static WCHAR emptyW[] = L""; + + /* Extract the parameters based on the tokens= value (There will always + be some value, as if it is not supplied, it defaults to tokens=1). + Rough logic: + Count how many tokens are named in the line, identify the lowest + Empty (set to null terminated string) that number of named variables + While lasttoken != nextlowest + %letter = parameter number 'nextlowest' + letter++ (if >26 or >52 abort) + Go through token= string finding next lowest number + If token ends in * set %letter = raw position of token(nextnumber+1) + */ + lasttoken = -1; + nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound, + &starfound, &thisduplicate); + + TRACE("Using var=%lc on %d max\n", for_var_index_to_char(varidx), totalfound); + /* Empty out variables */ + for (varoffset = 0; + varoffset < totalfound && for_var_index_in_range(varidx, varoffset); + varoffset++) + WCMD_set_for_loop_variable(varidx + varoffset, emptyW); + + /* Loop extracting the tokens + * Note: nexttoken of 0 means there were no tokens requested, to handle + * the special case of tokens=* + */ + varoffset = 0; + TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer)); + while (nexttoken > 0 && (nexttoken > lasttoken)) + { + anyduplicates |= thisduplicate; + + if (!for_var_index_in_range(varidx, varoffset)) + { + WARN("Out of range offset\n"); + break; + } + /* Extract the token number requested and set into the next variable context */ + parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, TRUE, FALSE, forf_delims); + TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken, + varidx + varoffset, wine_dbgstr_w(parm)); + if (parm) + { + WCMD_set_for_loop_variable(varidx + varoffset, parm); + varoffset++; + } + + /* Find the next token */ + lasttoken = nexttoken; + nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL, + &starfound, &thisduplicate); + } + /* If all the rest of the tokens were requested, and there is still space in + * the variable range, write them now + */ + if (!anyduplicates && starfound && for_var_index_in_range(varidx, varoffset)) + { + nexttoken++; + WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims); + TRACE("Parsed all remaining tokens (%d) as parameter %s\n", + varidx + varoffset, wine_dbgstr_w(parm)); + if (parm) + WCMD_set_for_loop_variable(varidx + varoffset, parm); + } + + /* Execute the body of the for loop with these values */ + if (forloopcontext->variable[varidx] && forloopcontext->variable[varidx][0] != forf_eol) + { + /* +3 for "do " */ + WCMD_part_execute(&cmdList, CMD_node_get_command(cmdList)->command + 3, FALSE, TRUE); + } + else + { + TRACE("Skipping line because of eol\n"); + cmdList = NULL; + } + return cmdList; +} + void WCMD_save_for_loop_context(BOOL reset) { FOR_CONTEXT *new = xalloc(sizeof(*new)); @@ -2871,6 +3091,116 @@ void WCMD_set_for_loop_variable(int var_idx, const WCHAR *value) forloopcontext->variable[var_idx] = xstrdupW(value); }
+static BOOL match_ending_delim(WCHAR *string) +{ + WCHAR *to = string + wcslen(string); + + /* strip trailing delim */ + if (to > string) to--; + if (to > string && *to == string[0]) + { + *to = L'\0'; + return TRUE; + } + WARN("Can't find ending delimiter (%ls)\n", string); + return FALSE; +} + +static CMD_NODE *for_control_execute_from_FILE(CMD_FOR_CONTROL *for_ctrl, FILE *input, CMD_NODE *cmdList) +{ + WCHAR buffer[MAXSTRING]; + int skip_count = for_ctrl->num_lines_to_skip; + CMD_NODE *body = NULL; + + /* Read line by line until end of file */ + while (fgetws(buffer, ARRAY_SIZE(buffer), input)) + { + size_t len; + + if (skip_count) + { + TRACE("skipping %d\n", skip_count); + skip_count--; + continue; + } + len = wcslen(buffer); + /* Either our buffer isn't large enough to fit a full line, or there's a stray + * '\0' in the buffer. + */ + if (!feof(input) && (len == 0 || (buffer[len - 1] != '\n' && buffer[len - 1] != '\r'))) + break; + while (len && (buffer[len - 1] == '\n' || buffer[len - 1] == '\r')) + buffer[--len] = L'\0'; + body = for_loop_fileset_parse_line(cmdList, for_ctrl->variable_index, buffer, + for_ctrl->eol, for_ctrl->delims, for_ctrl->tokens); + buffer[0] = 0; + } + return body; +} + +static CMD_NODE *for_control_execute_fileset(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *cmdList) +{ + WCHAR *args; + size_t len; + CMD_NODE *body = NULL; + FILE *input; + int i; + + args = WCMD_skip_leading_spaces((WCHAR *)for_ctrl->set); + for (len = wcslen(args); len && (args[len - 1] == L' ' || args[len - 1] == L'\t'); len--) + args[len - 1] = L'\0'; + if (args[0] == (for_ctrl->use_backq ? L''' : L'"') && match_ending_delim(args)) + { + args++; + if (!for_ctrl->num_lines_to_skip) + { + body = for_loop_fileset_parse_line(cmdList, for_ctrl->variable_index, args, + for_ctrl->eol, for_ctrl->delims, for_ctrl->tokens); + } + } + else if (args[0] == (for_ctrl->use_backq ? L'`' : L''') && match_ending_delim(args)) + { + WCHAR temp_cmd[MAX_PATH]; + + args++; + wsprintfW(temp_cmd, L"CMD.EXE /C %s", args); + TRACE("Reading output of '%s'\n", wine_dbgstr_w(temp_cmd)); + input = _wpopen(temp_cmd, L"rt,ccs=unicode"); + if (!input) + { + WCMD_print_error(); + WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), args); + errorlevel = 1; + return NULL; /* FOR loop aborts at first failure here */ + } + body = for_control_execute_from_FILE(for_ctrl, input, cmdList); + fclose(input); + } + else + { + for (i = 0; ; i++) + { + WCHAR *element = WCMD_parameter(args, i, NULL, TRUE, FALSE); + if (!element || !*element) break; + if (element[0] == L'"' && match_ending_delim(element)) element++; + /* Open the file, read line by line and process */ + TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(element)); + input = _wfopen(element, L"rt,ccs=unicode"); + if (!input) + { + WCMD_print_error(); + WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), element); + errorlevel = 1; + return NULL; /* FOR loop aborts at first failure here */ + } + body = for_control_execute_from_FILE(for_ctrl, input, cmdList); + fclose(input); + } + } + + return body; +} + static CMD_NODE *for_control_execute_numbers(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *cmdList) { WCHAR set[MAXSTRING]; @@ -2916,6 +3246,9 @@ void for_control_execute(CMD_FOR_CONTROL *for_ctrl, CMD_NODE **cmdList)
switch (for_ctrl->operator) { + case CMD_FOR_FILE_SET: + last = for_control_execute_fileset(for_ctrl, *cmdList); + break; case CMD_FOR_NUMBERS: last = for_control_execute_numbers(for_ctrl, *cmdList); break;
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/test_builtins.cmd.exp | 4 ++-- programs/cmd/wcmdmain.c | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index a7af684cb9a..84637ce41e3 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -1014,8 +1014,8 @@ A D B C B D --- nested FORs and args tempering -@todo_wine@inner argument {-foo, bar} -@todo_wine@inner argument {-x, y} +inner argument {-foo, bar} +inner argument {-x, y} --- nesting and delayed expansion a 1 2 1-A2 diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 5f7d399b4e7..9bce77b19df 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -3140,13 +3140,17 @@ static CMD_NODE *for_control_execute_from_FILE(CMD_FOR_CONTROL *for_ctrl, FILE *
static CMD_NODE *for_control_execute_fileset(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *cmdList) { + WCHAR set[MAXSTRING]; WCHAR *args; size_t len; CMD_NODE *body = NULL; FILE *input; int i;
- args = WCMD_skip_leading_spaces((WCHAR *)for_ctrl->set); + wcscpy(set, for_ctrl->set); + handleExpansion(set, context != NULL, delayedsubst); + + args = WCMD_skip_leading_spaces(set); for (len = wcslen(args); len && (args[len - 1] == L' ' || args[len - 1] == L'\t'); len--) args[len - 1] = L'\0'; if (args[0] == (for_ctrl->use_backq ? L''' : L'"') && match_ending_delim(args))