The aim of this serie is to split FOR loop instruction parsing from it's execution.
In more details: - introduces CMD_FOR_CONTROL structure to hold parsed FOR instruction - improves the support of FOR loop variables (%0-%9 are now available) - fixes a couple of issues in FOR instruction parsing (missing \t detection in a couple of places, quotes management), - fixes a couple of issues in FOR execution (mainly missing delayed substitution of some constructs) - code refactoring with helpers
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/test_builtins.cmd | 35 +++++++++++++++++++++++- programs/cmd/tests/test_builtins.cmd.exp | 30 +++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-)
diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index 02020a5a6ef..2ef2d495a63 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -1440,6 +1440,24 @@ for /F "tokens=1,* delims= " %%a in ("%WINE_ARGS%") do ( goto :test_for_loop_params_parse ) set "WINE_ARGS=" +echo --- nesting and delayed expansion +setlocal enabledelayedexpansion +set WINE_ARGS=1 +for %%a in (a b) do ( + set /a WINE_ARGS+=1 + echo %%a %WINE_ARGS% !WINE_ARGS! + for /l %%b in (1 1 !WINE_ARGS!) do ( + if !WINE_ARGS!==%%b (echo %%b-A1) else echo %%b-A2 + if %WINE_ARGS%==%%b (echo %%b-B1) else echo %%b-B2 + ) +) +setlocal disabledelayedexpansion +echo --- nesting if/for +for %%a in ("f" +"g" +"h" +) do if #==# (echo %%a) +echo ---
mkdir foobar & cd foobar mkdir foo @@ -1990,6 +2008,21 @@ echo.>> bar echo kkk>>bar for /f %%k in (foo bar) do echo %%k for /f %%k in (bar foo) do echo %%k +echo ------ quoting and file access +echo a > f.zzz +echo b >> f.zzz +erase f2.zzz +for /f %%a in (f.zzz) do echo A%%a +for /f %%a in ("f.zzz") do echo B%%a +for /f %%a in (f2.zzz) do echo C%%a +for /f %%a in ("f2.zzz") do echo D%%a +for /f "usebackq" %%a in (f.zzz) do echo E%%a +for /f "usebackq" %%a in ("f.zzz") do echo F%%a +for /f "usebackq" %%a in (f2.zzz) do echo G%%a +for /f "usebackq" %%a in ("f2.zzz") do echo H%%a +for /f %%a in (f*.zzz) do echo I%%a +for /f %%a in ("f*.zzz") do echo J%%a +erase f.zzz echo ------ command argument rem Not implemented on NT4, need to skip it as no way to get output otherwise if "%CD%"=="" goto :SkipFORFcmdNT4 @@ -2085,7 +2118,7 @@ echo 3.14>testfile FOR /F "tokens=*" %%A IN (testfile) DO @echo 1:%%A,%%B FOR /F "tokens=1*" %%A IN (testfile) DO @echo 2:%%A,%%B FOR /F "tokens=2*" %%A IN (testfile) DO @echo 3:%%A,%%B -FOR /F "tokens=1,* delims=." %%A IN (testfile) DO @echo 4:%%A,%%B +FOR /F "tokens=1,*@tab@delims=." %%A IN (testfile) DO @echo 4:%%A,%%B del testfile cd .. rd /s/q foobar diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 0f33f2ae05e..e6e6e21cd17 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -1016,6 +1016,24 @@ B D --- nested FORs and args tempering @todo_wine@inner argument {-foo, bar} @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 +--- nesting if/for +@todo_wine@"f" +@todo_wine@"g" +@todo_wine@"h" +@todo_wine@--- --- basic wildcards bazbaz --- wildcards in subdirs @@ -1302,6 +1320,16 @@ kkk a b c +------ quoting and file access +Aa +Ab +Bf.zzz +Df2.zzz +Ea +Eb +Fa +Fb +Jf*.zzz ------ command argument Passed1@or_broken@Missing functionality - Broken1 Passed2@or_broken@Missing functionality - Broken2 @@ -1357,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, -4:3,14 +@todo_wine@4:3,14 ------ parameter splitting :forFParameterSplittingFunc myparam1=myvalue1 myparam2=myparam2 mytest@space@@space@@space@ :forFParameterSplittingFunc myparam1=myvalue1 myparam2=myparam2 mytest@space@@space@@space@
From: Eric Pouech epouech@codeweavers.com
This should let the code be agnostic to the supported variables.
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/batch.c | 4 ++-- programs/cmd/builtins.c | 18 +++++++++--------- programs/cmd/wcmd.h | 22 ++++++++++++++++++++-- programs/cmd/wcmdmain.c | 6 +++--- 4 files changed, 34 insertions(+), 16 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index 455cc99083c..b67b14019f3 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -396,7 +396,7 @@ void WCMD_HandleTildeModifiers(WCHAR **start, BOOL atExecute) break;
} else { - int foridx = FOR_VAR_IDX(*lastModifier); + int foridx = for_var_char_to_index(*lastModifier); /* Its a valid parameter identifier - OK */ if ((foridx >= 0) && (forloopcontext.variable[foridx] != NULL)) break;
@@ -424,7 +424,7 @@ void WCMD_HandleTildeModifiers(WCHAR **start, BOOL atExecute) *lastModifier-'0' + context -> shift_count[*lastModifier-'0'], NULL, FALSE, TRUE)); } else { - int foridx = FOR_VAR_IDX(*lastModifier); + int foridx = for_var_char_to_index(*lastModifier); lstrcpyW(outputparam, forloopcontext.variable[foridx]); }
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index 76f75bb2d96..f94230e6fae 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -1953,7 +1953,7 @@ static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr, static void WCMD_parse_line(CMD_NODE *cmdStart, const WCHAR *firstCmd, CMD_NODE **cmdEnd, - const WCHAR variable, + int varidx, WCHAR *buffer, BOOL *doExecuted, int *forf_skip, @@ -1963,7 +1963,7 @@ static void WCMD_parse_line(CMD_NODE *cmdStart,
WCHAR *parm; FOR_CONTEXT oldcontext; - int varidx, varoffset; + int varoffset; int nexttoken, lasttoken = -1; BOOL starfound = FALSE; BOOL thisduplicate = FALSE; @@ -1994,11 +1994,10 @@ static void WCMD_parse_line(CMD_NODE *cmdStart, lasttoken = -1; nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound, &starfound, &thisduplicate); - varidx = FOR_VAR_IDX(variable);
/* Empty out variables */ for (varoffset=0; - varidx >= 0 && varoffset<totalfound && (((varidx%26) + varoffset) < 26); + varidx >= 0 && varoffset<totalfound && for_var_index_in_range(varidx, varoffset); varoffset++) { forloopcontext.variable[varidx + varoffset] = emptyW; } @@ -2011,6 +2010,8 @@ static void WCMD_parse_line(CMD_NODE *cmdStart, while (varidx >= 0 && (nexttoken > 0 && (nexttoken > lasttoken))) { anyduplicates |= thisduplicate;
+ if (!for_var_index_in_range(varidx, varoffset)) 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); WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken, @@ -2018,7 +2019,6 @@ static void WCMD_parse_line(CMD_NODE *cmdStart, if (varidx >=0) { if (parm) forloopcontext.variable[varidx + varoffset] = xstrdupW(parm); varoffset++; - if (((varidx%26)+varoffset) >= 26) break; }
/* Find the next token */ @@ -2029,7 +2029,7 @@ static void WCMD_parse_line(CMD_NODE *cmdStart,
/* If all the rest of the tokens were requested, and there is still space in the variable range, write them now */ - if (!anyduplicates && starfound && varidx >= 0 && (((varidx%26) + varoffset) < 26)) { + if (!anyduplicates && starfound && varidx >= 0 && for_var_index_in_range(varidx, varoffset)) { nexttoken++; WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims); WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n", @@ -2222,7 +2222,7 @@ void WCMD_for (WCHAR *p, CMD_NODE **cmdList) { /* Variable should follow */ lstrcpyW(variable, thisArg); WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable)); - varidx = FOR_VAR_IDX(variable[1]); + varidx = for_var_char_to_index(variable[1]);
/* Ensure line continues with IN */ thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE); @@ -2429,7 +2429,7 @@ void WCMD_for (WCHAR *p, CMD_NODE **cmdList) { break; while (len && (buffer[len - 1] == '\n' || buffer[len - 1] == '\r')) buffer[--len] = L'\0'; - WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted, + WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, for_var_char_to_index(variable[1]), buffer, &doExecuted, &forf_skip, forf_eol, forf_delims, forf_tokens); buffer[0] = 0; } @@ -2457,7 +2457,7 @@ void WCMD_for (WCHAR *p, CMD_NODE **cmdList) {
/* Copy the item away from the global buffer used by WCMD_parameter */ lstrcpyW(buffer, itemStart); - WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted, + WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, for_var_char_to_index(variable[1]), buffer, &doExecuted, &forf_skip, forf_eol, forf_delims, forf_tokens);
/* Only one string can be supplied in the whole set, abort future set processing */ diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index def31d7e2d3..d556cd59fe8 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -267,8 +267,26 @@ typedef struct _DIRECTORY_STACK /* Data structure to for loop variables during for body execution, bearing in mind that for loops can be nested */ #define MAX_FOR_VARIABLES 52 -#define FOR_VAR_IDX(c) (((c)>='a'&&(c)<='z')?((c)-'a'):\ - ((c)>='A'&&(c)<='Z')?(26+(c)-'A'):-1) + +static inline int for_var_char_to_index(WCHAR c) +{ + if (c >= L'a' && c <= L'z') return c - L'a'; + if (c >= L'A' && c <= L'Z') return c - L'A' + 26; + return -1; +} + +static inline WCHAR for_var_index_to_char(int var_idx) +{ + if (var_idx < 0 || var_idx >= MAX_FOR_VARIABLES) return L'?'; + if (var_idx < 26) return L'a' + var_idx; + return L'A' + var_idx - 26; +} + +/* check that the range [var_idx, var_idx + var_offset] is a contiguous range */ +static inline BOOL for_var_index_in_range(int var_idx, int var_offset) +{ + return for_var_char_to_index(for_var_index_to_char(var_idx) + var_offset) == var_idx + var_offset; +}
typedef struct _FOR_CONTEXT { WCHAR *variable[MAX_FOR_VARIABLES]; /* a-z then A-Z */ diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 5f1a94b39b3..9e5f1eae241 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -807,10 +807,10 @@ static void handleExpansion(WCHAR *cmd, BOOL atExecute, BOOL delayed) { WCHAR *normalp;
/* Display the FOR variables in effect */ - for (i=0;i<52;i++) { + for (i=0;i<MAX_FOR_VARIABLES;i++) { if (forloopcontext.variable[i]) { WINE_TRACE("FOR variable context: %c = '%s'\n", - i<26?i+'a':(i-26)+'A', + for_var_index_to_char(i), wine_dbgstr_w(forloopcontext.variable[i])); } } @@ -859,7 +859,7 @@ static void handleExpansion(WCHAR *cmd, BOOL atExecute, BOOL delayed) { WCMD_strsubstW(p, p+2, NULL, 0);
} else { - int forvaridx = FOR_VAR_IDX(*(p+1)); + int forvaridx = for_var_char_to_index(*(p+1)); if (startchar == '%' && forvaridx != -1 && forloopcontext.variable[forvaridx]) { /* Replace the 2 characters, % and for variable character */ WCMD_strsubstW(p, p + 2, forloopcontext.variable[forvaridx], -1);
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/batch.c | 11 ++++---- programs/cmd/builtins.c | 58 ++++++++++++++++------------------------- programs/cmd/wcmd.h | 12 ++++++--- programs/cmd/wcmdmain.c | 52 ++++++++++++++++++++++++++++++++---- 4 files changed, 84 insertions(+), 49 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index b67b14019f3..d85cbf810aa 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -398,7 +398,7 @@ void WCMD_HandleTildeModifiers(WCHAR **start, BOOL atExecute) } else { int foridx = for_var_char_to_index(*lastModifier); /* Its a valid parameter identifier - OK */ - if ((foridx >= 0) && (forloopcontext.variable[foridx] != NULL)) break; + if ((foridx >= 0) && (forloopcontext->variable[foridx] != NULL)) break;
/* Its not a valid parameter identifier - step backwards */ lastModifier--; @@ -425,7 +425,8 @@ void WCMD_HandleTildeModifiers(WCHAR **start, BOOL atExecute) NULL, FALSE, TRUE)); } else { int foridx = for_var_char_to_index(*lastModifier); - lstrcpyW(outputparam, forloopcontext.variable[foridx]); + if (foridx != -1) + lstrcpyW(outputparam, forloopcontext->variable[foridx]); }
/* 1. Handle '~' : Strip surrounding quotes */ @@ -663,12 +664,10 @@ void WCMD_call (WCHAR *command) { if (context) {
LARGE_INTEGER li; - FOR_CONTEXT oldcontext;
/* Save the for variable context, then start with an empty context as for loop variables do not survive a call */ - oldcontext = forloopcontext; - memset(&forloopcontext, 0, sizeof(forloopcontext)); + WCMD_save_for_loop_context(TRUE);
/* Save the current file position, call the same file, restore position */ @@ -680,7 +679,7 @@ void WCMD_call (WCHAR *command) { &li.u.HighPart, FILE_BEGIN);
/* Restore the for loop context */ - forloopcontext = oldcontext; + WCMD_restore_for_loop_context(); } else { WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_CALLINSCRIPT)); } diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index f94230e6fae..f036f840958 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -1962,7 +1962,6 @@ static void WCMD_parse_line(CMD_NODE *cmdStart, WCHAR *forf_tokens) {
WCHAR *parm; - FOR_CONTEXT oldcontext; int varoffset; int nexttoken, lasttoken = -1; BOOL starfound = FALSE; @@ -1978,7 +1977,7 @@ static void WCMD_parse_line(CMD_NODE *cmdStart, }
/* Save away any existing for variable context (e.g. nested for loops) */ - oldcontext = forloopcontext; + WCMD_save_for_loop_context(FALSE);
/* 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). @@ -1999,7 +1998,7 @@ static void WCMD_parse_line(CMD_NODE *cmdStart, for (varoffset=0; varidx >= 0 && varoffset<totalfound && for_var_index_in_range(varidx, varoffset); varoffset++) { - forloopcontext.variable[varidx + varoffset] = emptyW; + WCMD_set_for_loop_variable(varidx + varoffset, emptyW); }
/* Loop extracting the tokens @@ -2017,7 +2016,8 @@ static void WCMD_parse_line(CMD_NODE *cmdStart, WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken, varidx + varoffset, wine_dbgstr_w(parm)); if (varidx >=0) { - if (parm) forloopcontext.variable[varidx + varoffset] = xstrdupW(parm); + if (parm) + WCMD_set_for_loop_variable(varidx + varoffset, parm); varoffset++; }
@@ -2034,30 +2034,19 @@ static void WCMD_parse_line(CMD_NODE *cmdStart, WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims); WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n", varidx + varoffset, wine_dbgstr_w(parm)); - if (parm) forloopcontext.variable[varidx + varoffset] = xstrdupW(parm); + if (parm) + WCMD_set_for_loop_variable(varidx + varoffset, parm); }
/* Execute the body of the foor loop with these values */ - if (varidx >= 0 && forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) { + if (varidx >= 0 && forloopcontext->variable[varidx] && forloopcontext->variable[varidx][0] != forf_eol) { CMD_NODE *thisCmdStart = cmdStart; *doExecuted = TRUE; WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE); *cmdEnd = thisCmdStart; }
- /* Free the duplicated strings, and restore the context */ - if (varidx >=0) { - int i; - for (i=varidx; i<MAX_FOR_VARIABLES; i++) { - if ((forloopcontext.variable[i] != oldcontext.variable[i]) && - (forloopcontext.variable[i] != emptyW)) { - free(forloopcontext.variable[i]); - } - } - } - - /* Restore the original for variable contextx */ - forloopcontext = oldcontext; + WCMD_restore_for_loop_context(); }
/************************************************************************** @@ -2077,7 +2066,7 @@ static void WCMD_parse_line(CMD_NODE *cmdStart, * * Returns a file handle which can be used to read the input lines from. */ -static FILE* WCMD_forf_getinput(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) +static FILE *WCMD_forf_getinput(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) { WCHAR *trimmed = NULL; FILE* ret; @@ -2132,7 +2121,6 @@ void WCMD_for (WCHAR *p, CMD_NODE **cmdList) { CMD_NODE *setStart, *thisSet, *cmdStart, *cmdEnd; WCHAR variable[4]; int varidx = -1; - WCHAR *oldvariablevalue; WCHAR *firstCmd; int thisDepth; WCHAR optionsRoot[MAX_PATH]; @@ -2352,14 +2340,13 @@ void WCMD_for (WCHAR *p, CMD_NODE **cmdList) { } doExecuted = TRUE;
+ WCMD_save_for_loop_context(FALSE); /* Save away any existing for variable context (e.g. nested for loops) and restore it after executing the body of this for loop */ - if (varidx >= 0) { - oldvariablevalue = forloopcontext.variable[varidx]; - forloopcontext.variable[varidx] = fullitem; - } + if (varidx >= 0) + WCMD_set_for_loop_variable(varidx, fullitem); WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE); - if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue; + WCMD_restore_for_loop_context();
cmdEnd = thisCmdStart; } @@ -2369,14 +2356,13 @@ void WCMD_for (WCHAR *p, CMD_NODE **cmdList) { } else { doExecuted = TRUE;
+ WCMD_save_for_loop_context(FALSE); /* Save away any existing for variable context (e.g. nested for loops) and restore it after executing the body of this for loop */ - if (varidx >= 0) { - oldvariablevalue = forloopcontext.variable[varidx]; - forloopcontext.variable[varidx] = fullitem; - } + if (varidx >= 0) + WCMD_set_for_loop_variable(varidx, fullitem); WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE); - if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue; + WCMD_restore_for_loop_context();
cmdEnd = thisCmdStart; } @@ -2491,12 +2477,14 @@ void WCMD_for (WCHAR *p, CMD_NODE **cmdList) {
/* Save away any existing for variable context (e.g. nested for loops) and restore it after executing the body of this for loop */ - if (varidx >= 0) { - oldvariablevalue = forloopcontext.variable[varidx]; - forloopcontext.variable[varidx] = thisNum; + if (varidx >= 0) + { + WCMD_save_for_loop_context(FALSE); + WCMD_set_for_loop_variable(varidx, thisNum); } WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE); - if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue; + if (varidx >= 0) + WCMD_restore_for_loop_context(); } cmdEnd = thisCmdStart; } diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index d556cd59fe8..9d4f55bc414 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -288,10 +288,16 @@ static inline BOOL for_var_index_in_range(int var_idx, int var_offset) return for_var_char_to_index(for_var_index_to_char(var_idx) + var_offset) == var_idx + var_offset; }
-typedef struct _FOR_CONTEXT { - WCHAR *variable[MAX_FOR_VARIABLES]; /* a-z then A-Z */ +typedef struct _FOR_CONTEXT +{ + struct _FOR_CONTEXT *previous; + WCHAR *variable[MAX_FOR_VARIABLES]; /* a-z then A-Z */ } FOR_CONTEXT;
+void WCMD_save_for_loop_context(BOOL reset); +void WCMD_restore_for_loop_context(void); +void WCMD_set_for_loop_variable(int var_idx, const WCHAR *value); + /* * Global variables quals, param1, param2 contain the current qualifiers * (uppercased and concatenated) and parameters entered, with environment @@ -300,7 +306,7 @@ typedef struct _FOR_CONTEXT { extern WCHAR quals[MAXSTRING], param1[MAXSTRING], param2[MAXSTRING]; extern int errorlevel; extern BATCH_CONTEXT *context; -extern FOR_CONTEXT forloopcontext; +extern FOR_CONTEXT *forloopcontext; extern BOOL delayedsubst;
#endif /* !RC_INVOKED */ diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 9e5f1eae241..da9d93f4dde 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -39,7 +39,7 @@ BATCH_CONTEXT *context = NULL; int errorlevel; WCHAR quals[MAXSTRING], param1[MAXSTRING], param2[MAXSTRING]; BOOL interactive; -FOR_CONTEXT forloopcontext; /* The 'for' loop context */ +FOR_CONTEXT *forloopcontext; /* The 'for' loop context */ BOOL delayedsubst = FALSE; /* The current delayed substitution setting */
int defaultColor = 7; @@ -808,10 +808,10 @@ static void handleExpansion(WCHAR *cmd, BOOL atExecute, BOOL delayed) {
/* Display the FOR variables in effect */ for (i=0;i<MAX_FOR_VARIABLES;i++) { - if (forloopcontext.variable[i]) { + if (forloopcontext->variable[i]) { WINE_TRACE("FOR variable context: %c = '%s'\n", for_var_index_to_char(i), - wine_dbgstr_w(forloopcontext.variable[i])); + wine_dbgstr_w(forloopcontext->variable[i])); } }
@@ -860,9 +860,9 @@ static void handleExpansion(WCHAR *cmd, BOOL atExecute, BOOL delayed) {
} else { int forvaridx = for_var_char_to_index(*(p+1)); - if (startchar == '%' && forvaridx != -1 && forloopcontext.variable[forvaridx]) { + if (startchar == '%' && forvaridx != -1 && forloopcontext->variable[forvaridx]) { /* Replace the 2 characters, % and for variable character */ - WCMD_strsubstW(p, p + 2, forloopcontext.variable[forvaridx], -1); + WCMD_strsubstW(p, p + 2, forloopcontext->variable[forvaridx], -1); } else if (!atExecute || startchar == '!') { p = WCMD_expand_envvar(p, startchar);
@@ -2710,6 +2710,44 @@ BOOL if_condition_evaluate(CMD_IF_CONDITION *cond, int *test) return TRUE; }
+void WCMD_save_for_loop_context(BOOL reset) +{ + FOR_CONTEXT *new = xalloc(sizeof(*new)); + if (reset) + memset(new, 0, sizeof(*new)); + else /* clone existing */ + *new = *forloopcontext; + new->previous = forloopcontext; + forloopcontext = new; +} + +void WCMD_restore_for_loop_context(void) +{ + FOR_CONTEXT *old = forloopcontext->previous; + int varidx; + if (!old) + { + FIXME("Unexpected situation\n"); + return; + } + for (varidx = 0; varidx < MAX_FOR_VARIABLES; varidx++) + { + if (forloopcontext->variable[varidx] != old->variable[varidx]) + free(forloopcontext->variable[varidx]); + } + free(forloopcontext); + forloopcontext = old; +} + +void WCMD_set_for_loop_variable(int var_idx, const WCHAR *value) +{ + if (var_idx < 0 || var_idx >= MAX_FOR_VARIABLES) return; + if (forloopcontext->previous && + forloopcontext->previous->variable[var_idx] != forloopcontext->variable[var_idx]) + free(forloopcontext->variable[var_idx]); + forloopcontext->variable[var_idx] = xstrdupW(value); +} + /*************************************************************************** * WCMD_process_commands * @@ -2799,6 +2837,10 @@ int __cdecl wmain (int argc, WCHAR *argvW[]) LocalFree(cmd); cmd = NULL;
+ /* init for loop context */ + forloopcontext = NULL; + WCMD_save_for_loop_context(TRUE); + /* Can't use argc/argv as it will have stripped quotes from parameters * meaning cmd.exe /C echo "quoted string" is impossible */
From: Eric Pouech epouech@codeweavers.com
Note: there are many other letters than can be used as variable names.
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/test_builtins.cmd.exp | 4 ++-- programs/cmd/wcmd.h | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index e6e6e21cd17..1c3b0dc81cc 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -613,8 +613,8 @@ N @todo_wine@foo @todo_wine@bar --- in digit variables -@todo_wine@a %1 %2 -@todo_wine@b %1 %2 +a %1 %2 +b %1 %2 ------------ Testing parameter zero ------------ :func parm1 [:func] [@drive@] [@path@] [test] [.cmd] [@drive@@shortpath@test.cmd] diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index 9d4f55bc414..dde5d1e12ec 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -266,12 +266,13 @@ typedef struct _DIRECTORY_STACK
/* Data structure to for loop variables during for body execution, bearing in mind that for loops can be nested */ -#define MAX_FOR_VARIABLES 52 +#define MAX_FOR_VARIABLES (2*26+10)
static inline int for_var_char_to_index(WCHAR c) { if (c >= L'a' && c <= L'z') return c - L'a'; if (c >= L'A' && c <= L'Z') return c - L'A' + 26; + if (c >= L'0' && c <= L'9') return c - L'0' + 2 * 26; return -1; }
@@ -279,7 +280,8 @@ static inline WCHAR for_var_index_to_char(int var_idx) { if (var_idx < 0 || var_idx >= MAX_FOR_VARIABLES) return L'?'; if (var_idx < 26) return L'a' + var_idx; - return L'A' + var_idx - 26; + if (var_idx < 52) return L'A' + var_idx - 26; + return L'0' + var_idx - 52; }
/* check that the range [var_idx, var_idx + var_offset] is a contiguous range */
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/builtins.c | 129 +++++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 55 deletions(-)
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index f036f840958..b28638ce70d 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -153,6 +153,41 @@ static struct {' ',' '} };
+static DIRECTORY_STACK *WCMD_dir_stack_create(const WCHAR *dir, const WCHAR *file) +{ + DIRECTORY_STACK *new = xalloc(sizeof(DIRECTORY_STACK)); + + new->next = NULL; + new->fileName = NULL; + if (!dir && !file) + { + DWORD sz = GetCurrentDirectoryW(0, NULL); + new->dirName = xalloc(sz * sizeof(WCHAR)); + GetCurrentDirectoryW(sz, new->dirName); + } + else if (!file) + new->dirName = xstrdupW(dir); + else + { + new->dirName = xalloc((wcslen(dir) + 1 + wcslen(file) + 1) * sizeof(WCHAR)); + wcscpy(new->dirName, dir); + wcscat(new->dirName, L"\"); + wcscat(new->dirName, file); + } + return new; +} + +static DIRECTORY_STACK *WCMD_dir_stack_free(DIRECTORY_STACK *dir) +{ + DIRECTORY_STACK *next; + + if (!dir) return NULL; + next = dir->next; + free(dir->dirName); + free(dir); + return next; +} + /************************************************************************** * WCMD_ask_confirm * @@ -1371,26 +1406,18 @@ static BOOL WCMD_delete_one (const WCHAR *thisArg) { WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
/* Allocate memory, add to list */ - nextDir = xalloc(sizeof(DIRECTORY_STACK)); + nextDir = WCMD_dir_stack_create(subParm, NULL); if (allDirs == NULL) allDirs = nextDir; if (lastEntry != NULL) lastEntry->next = nextDir; lastEntry = nextDir; - nextDir->next = NULL; - nextDir->dirName = xstrdupW(subParm); } } while (FindNextFileW(hff, &fd) != 0); FindClose (hff);
/* Go through each subdir doing the delete */ while (allDirs != NULL) { - DIRECTORY_STACK *tempDir; - - tempDir = allDirs->next; found |= WCMD_delete_one (allDirs->dirName); - - free(allDirs->dirName); - free(allDirs); - allDirs = tempDir; + allDirs = WCMD_dir_stack_free(allDirs); } } } @@ -1763,47 +1790,44 @@ static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip, * processed, and any other directory still to be processed, mimicking what * Windows does */ -static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) { - DIRECTORY_STACK *remainingDirs = dirsToWalk; - WCHAR fullitem[MAX_PATH]; - WIN32_FIND_DATAW fd; - HANDLE hff; +static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) +{ + DIRECTORY_STACK *remainingDirs = dirsToWalk; + WCHAR fullitem[MAX_PATH]; + WIN32_FIND_DATAW fd; + HANDLE hff;
- /* Build a generic search and add all directories on the list of directories - still to walk */ - lstrcpyW(fullitem, dirsToWalk->dirName); - lstrcatW(fullitem, L"\*"); - hff = FindFirstFileW(fullitem, &fd); - if (hff != INVALID_HANDLE_VALUE) { - do { - WINE_TRACE("Looking for subdirectories\n"); - if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && - (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0)) - { - /* Allocate memory, add to list */ - DIRECTORY_STACK *toWalk; - if (wcslen(dirsToWalk->dirName) + 1 + wcslen(fd.cFileName) >= MAX_PATH) + /* Build a generic search and add all directories on the list of directories + still to walk */ + lstrcpyW(fullitem, dirsToWalk->dirName); + lstrcatW(fullitem, L"\*"); + if ((hff = FindFirstFileW(fullitem, &fd)) == INVALID_HANDLE_VALUE) return; + + do + { + TRACE("Looking for subdirectories\n"); + if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && + (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0)) { - WINE_TRACE("Skipping too long path %s\%s\n", - debugstr_w(dirsToWalk->dirName), debugstr_w(fd.cFileName)); - continue; + /* Allocate memory, add to list */ + DIRECTORY_STACK *toWalk; + if (wcslen(dirsToWalk->dirName) + 1 + wcslen(fd.cFileName) >= MAX_PATH) + { + TRACE("Skipping too long path %s\%s\n", + debugstr_w(dirsToWalk->dirName), debugstr_w(fd.cFileName)); + continue; + } + toWalk = WCMD_dir_stack_create(dirsToWalk->dirName, fd.cFileName); + TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next); + toWalk->next = remainingDirs->next; + remainingDirs->next = toWalk; + remainingDirs = toWalk; + TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName), + toWalk, toWalk->next); } - toWalk = xalloc(sizeof(DIRECTORY_STACK)); - WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next); - toWalk->next = remainingDirs->next; - remainingDirs->next = toWalk; - remainingDirs = toWalk; - toWalk->dirName = xalloc(sizeof(WCHAR) * (wcslen(dirsToWalk->dirName) + 2 + wcslen(fd.cFileName))); - lstrcpyW(toWalk->dirName, dirsToWalk->dirName); - lstrcatW(toWalk->dirName, L"\"); - lstrcatW(toWalk->dirName, fd.cFileName); - WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName), - toWalk, toWalk->next); - } } while (FindNextFileW(hff, &fd) != 0); - WINE_TRACE("Finished adding all subdirectories\n"); - FindClose (hff); - } + TRACE("Finished adding all subdirectories\n"); + FindClose(hff); }
/************************************************************************** @@ -2201,10 +2225,8 @@ void WCMD_for (WCHAR *p, CMD_NODE **cmdList) { /* Set up the list of directories to recurse if we are going to */ } else if (doRecurse) { /* Allocate memory, add to list */ - dirsToWalk = xalloc(sizeof(DIRECTORY_STACK)); - dirsToWalk->next = NULL; - dirsToWalk->dirName = xstrdupW(optionsRoot); - WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName)); + dirsToWalk = WCMD_dir_stack_create(optionsRoot, NULL); + TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName)); }
/* Variable should follow */ @@ -2491,10 +2513,7 @@ void WCMD_for (WCHAR *p, CMD_NODE **cmdList) {
/* If we are walking directories, move on to any which remain */ if (dirsToWalk != NULL) { - DIRECTORY_STACK *nextDir = dirsToWalk->next; - free(dirsToWalk->dirName); - free(dirsToWalk); - dirsToWalk = nextDir; + dirsToWalk = WCMD_dir_stack_free(dirsToWalk); if (dirsToWalk) WINE_TRACE("Moving to next directory to iterate: %s\n", wine_dbgstr_w(dirsToWalk->dirName)); else WINE_TRACE("Finished all directories.\n");
From: Eric Pouech epouech@codeweavers.com
Introducing structure to describe the various for options.
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/builtins.c | 713 +---------------------- programs/cmd/tests/test_builtins.cmd | 9 +- programs/cmd/tests/test_builtins.cmd.exp | 36 +- programs/cmd/wcmd.h | 44 ++ programs/cmd/wcmdmain.c | 657 +++++++++++++++++++++ 5 files changed, 748 insertions(+), 711 deletions(-)
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index b28638ce70d..e10dbbb134b 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -153,13 +153,13 @@ static struct {' ',' '} };
-static DIRECTORY_STACK *WCMD_dir_stack_create(const WCHAR *dir, const WCHAR *file) +DIRECTORY_STACK *WCMD_dir_stack_create(const WCHAR *dir, const WCHAR *file) { DIRECTORY_STACK *new = xalloc(sizeof(DIRECTORY_STACK));
new->next = NULL; new->fileName = NULL; - if (!dir && !file) + if ((!dir || !wcscmp(dir, L".")) && !file) { DWORD sz = GetCurrentDirectoryW(0, NULL); new->dirName = xalloc(sz * sizeof(WCHAR)); @@ -177,7 +177,7 @@ static DIRECTORY_STACK *WCMD_dir_stack_create(const WCHAR *dir, const WCHAR *fil return new; }
-static DIRECTORY_STACK *WCMD_dir_stack_free(DIRECTORY_STACK *dir) +DIRECTORY_STACK *WCMD_dir_stack_free(DIRECTORY_STACK *dir) { DIRECTORY_STACK *next;
@@ -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); @@ -1673,110 +1673,6 @@ static void WCMD_part_execute(CMD_NODE **cmdList, const WCHAR *firstcmd, } }
-static BOOL option_equals(WCHAR **haystack, const WCHAR *needle) -{ - int len = wcslen(needle); - - if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, - *haystack, len, needle, len) == CSTR_EQUAL) { - *haystack += len; - return TRUE; - } - - return FALSE; -} - -/***************************************************************************** - * WCMD_parse_forf_options - * - * Parses the for /f 'options', extracting the values and validating the - * keywords. Note all keywords are optional. - * Parameters: - * options [I] The unparsed parameter string - * eol [O] Set to the comment character (eol=x) - * skip [O] Set to the number of lines to skip (skip=xx) - * delims [O] Set to the token delimiters (delims=) - * tokens [O] Set to the requested tokens, as provided (tokens=) - * usebackq [O] Set to TRUE if usebackq found - * - * Returns TRUE on success, FALSE on syntax error - * - */ -static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip, - WCHAR *delims, WCHAR *tokens, BOOL *usebackq) -{ - - WCHAR *pos = options; - int len = lstrlenW(pos); - - /* Initialize to defaults */ - lstrcpyW(delims, L" \t"); - lstrcpyW(tokens, L"1"); - *eol = 0; - *skip = 0; - *usebackq = FALSE; - - /* Strip (optional) leading and trailing quotes */ - if ((*pos == '"') && (pos[len-1] == '"')) { - pos[len-1] = 0; - pos++; - } - - /* Process each keyword */ - while (pos && *pos) { - if (*pos == ' ' || *pos == '\t') { - pos++; - - /* Save End of line character (Ignore line if first token (based on delims) starts with it) */ - } else if (option_equals(&pos, L"eol=")) { - *eol = *pos++; - WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol); - - /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */ - } else if (option_equals(&pos, L"skip=")) { - WCHAR *nextchar = NULL; - *skip = wcstoul(pos, &nextchar, 0); - WINE_TRACE("Found skip as %d lines\n", *skip); - pos = nextchar; - - /* Save if usebackq semantics are in effect */ - } else if (option_equals(&pos, L"usebackq")) { - *usebackq = TRUE; - WINE_TRACE("Found usebackq\n"); - - /* Save the supplied delims. Slightly odd as space can be a delimiter but only - if you finish the optionsroot string with delims= otherwise the space is - just a token delimiter! */ - } else if (option_equals(&pos, L"delims=")) { - int i=0; - - while (*pos && *pos != ' ') { - delims[i++] = *pos; - pos++; - } - if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos; - delims[i++] = 0; /* Null terminate the delims */ - WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims)); - - /* Save the tokens being requested */ - } else if (option_equals(&pos, L"tokens=")) { - int i=0; - - while (*pos && *pos != ' ') { - tokens[i++] = *pos; - pos++; - } - tokens[i++] = 0; /* Null terminate the tokens */ - WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens)); - - } else { - WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos)); - return FALSE; - } - } - return TRUE; -} - /***************************************************************************** * WCMD_add_dirstowalk * @@ -1790,7 +1686,7 @@ static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip, * processed, and any other directory still to be processed, mimicking what * Windows does */ -static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) +void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) { DIRECTORY_STACK *remainingDirs = dirsToWalk; WCHAR fullitem[MAX_PATH]; @@ -1850,11 +1746,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; @@ -1953,592 +1849,29 @@ static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr, return nexttoken; }
-/************************************************************************** - * WCMD_parse_line - * - * When parsing file or string contents (for /f), once the string to parse - * has been identified, handle the various options and call the do part - * if appropriate. - * - * Parameters: - * cmdStart [I] - Identifies the list of commands making up the - * for loop body (especially if brackets in use) - * firstCmd [I] - The textual start of the command after the DO - * which is within the first item of cmdStart - * cmdEnd [O] - Identifies where to continue after the DO - * variable [I] - The variable identified on the for line - * buffer [I] - The string to parse - * doExecuted [O] - Set to TRUE if the DO is ever executed once - * forf_skip [I/O] - How many lines to skip first - * forf_eol [I] - The 'end of line' (comment) character - * forf_delims [I] - The delimiters to use when breaking the string apart - * forf_tokens [I] - The tokens to use when breaking the string apart - */ -static void WCMD_parse_line(CMD_NODE *cmdStart, - const WCHAR *firstCmd, - CMD_NODE **cmdEnd, - int varidx, - WCHAR *buffer, - BOOL *doExecuted, - int *forf_skip, - WCHAR forf_eol, - WCHAR *forf_delims, - 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""; - - /* Skip lines if requested */ - if (*forf_skip) { - (*forf_skip)--; - return; - } - - /* Save away any existing for variable context (e.g. nested for loops) */ - WCMD_save_for_loop_context(FALSE); - - /* 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); - - /* Empty out variables */ - for (varoffset=0; - varidx >= 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; - WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer)); - while (varidx >= 0 && (nexttoken > 0 && (nexttoken > lasttoken))) { - anyduplicates |= thisduplicate; - - if (!for_var_index_in_range(varidx, varoffset)) 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); - WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken, - varidx + varoffset, wine_dbgstr_w(parm)); - if (varidx >=0) { - 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 && varidx >= 0 && for_var_index_in_range(varidx, varoffset)) { - nexttoken++; - WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims); - WINE_TRACE("Parsed allremaining 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 foor loop with these values */ - if (varidx >= 0 && forloopcontext->variable[varidx] && forloopcontext->variable[varidx][0] != forf_eol) { - CMD_NODE *thisCmdStart = cmdStart; - *doExecuted = TRUE; - WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE); - *cmdEnd = thisCmdStart; - } - - WCMD_restore_for_loop_context(); -} - -/************************************************************************** - * WCMD_forf_getinput - * - * Return a FILE* which can be used for reading the input lines, - * either to a specific file (which may be quote delimited as we have to - * read the parameters in raw mode) or to a command which we need to - * execute. The command being executed runs in its own shell and stores - * its data in a temporary file. - * - * Parameters: - * usebackq [I] - Indicates whether usebackq is in effect or not - * itemStr [I] - The item to be handled, either a filename or - * whole command string to execute - * iscmd [I] - Identifies whether this is a command or not - * - * Returns a file handle which can be used to read the input lines from. - */ -static FILE *WCMD_forf_getinput(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) +void WCMD_for (WCHAR *p, CMD_NODE **cmdList) { - WCHAR *trimmed = NULL; - FILE* ret; + CMD_FOR_CONTROL *for_ctrl;
- /* Remove leading and trailing character (but there may be trailing whitespace too) */ - if ((iscmd && (itemstr[0] == '`' && usebackq)) || - (iscmd && (itemstr[0] == ''' && !usebackq)) || - (!iscmd && (itemstr[0] == '"' && usebackq))) + for_ctrl = for_control_parse(p); + if (!for_ctrl) { - trimmed = WCMD_strtrim(itemstr); - if (trimmed) - itemstr = trimmed; - itemstr[lstrlenW(itemstr)-1] = 0x00; - itemstr++; - } - - if (iscmd) - { - WCHAR temp_cmd[MAXSTRING]; - wsprintfW(temp_cmd, L"CMD.EXE /C %s", itemstr); - WINE_TRACE("Reading output of '%s'\n", wine_dbgstr_w(temp_cmd)); - ret = _wpopen(temp_cmd, L"rt,ccs=unicode"); - } - else - { - /* Open the file, read line by line and process */ - WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr)); - ret = _wfopen(itemstr, L"rt,ccs=unicode"); + *cmdList = NULL; + return; } - free(trimmed); - return ret; -} - -/************************************************************************** - * WCMD_for - * - * Batch file loop processing. - * - * On entry: cmdList contains the syntax up to the set - * next cmdList and all in that bracket contain the set data - * next cmdlist contains the DO cmd - * following that is either brackets or && entries (as per if) - * - */ - -void WCMD_for (WCHAR *p, CMD_NODE **cmdList) { - - WIN32_FIND_DATAW fd; - HANDLE hff; - int i; - const int in_len = lstrlenW(L"in"); - CMD_NODE *setStart, *thisSet, *cmdStart, *cmdEnd; - WCHAR variable[4]; - int varidx = -1; - WCHAR *firstCmd; - int thisDepth; - WCHAR optionsRoot[MAX_PATH]; - DIRECTORY_STACK *dirsToWalk = NULL; - BOOL expandDirs = FALSE; - BOOL useNumbers = FALSE; - BOOL doFileset = FALSE; - BOOL doRecurse = FALSE; - BOOL doExecuted = FALSE; /* Has the 'do' part been executed */ - LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */ - int itemNum; - CMD_NODE *thisCmdStart; - int parameterNo = 0; - WCHAR forf_eol = 0; - int forf_skip = 0; - WCHAR forf_delims[256]; - WCHAR forf_tokens[MAXSTRING]; - BOOL forf_usebackq = FALSE; - - /* Handle optional qualifiers (multiple are allowed) */ - WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE); - - optionsRoot[0] = 0; - while (thisArg && *thisArg == '/') { - WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg)); - thisArg++; - switch (towupper(*thisArg)) { - case 'D': expandDirs = TRUE; break; - case 'L': useNumbers = TRUE; break; - - /* Recursive is special case - /R can have an optional path following it */ - /* filenamesets are another special case - /F can have an optional options following it */ - case 'R': - case 'F': - { - /* When recursing directories, use current directory as the starting point unless - subsequently overridden */ - doRecurse = (towupper(*thisArg) == 'R'); - if (doRecurse) GetCurrentDirectoryW(ARRAY_SIZE(optionsRoot), optionsRoot); - - doFileset = (towupper(*thisArg) == 'F'); - - /* Retrieve next parameter to see if is root/options (raw form required - with for /f, or unquoted in for /r) */ - thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE); - - /* Next parm is either qualifier, path/options or variable - - only care about it if it is the path/options */ - if (thisArg && *thisArg != '/' && *thisArg != '%') { - parameterNo++; - lstrcpyW(optionsRoot, thisArg); - } - break; - } - default: - WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg); - }
- /* Step to next token */ - thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE); - } - - /* Ensure line continues with variable */ - if (*thisArg != '%') { - WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR)); - return; - } - - /* With for /f parse the options if provided */ - if (doFileset) { - if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip, - forf_delims, forf_tokens, &forf_usebackq)) + for (*cmdList = CMD_node_next(*cmdList); /* swallow options */ + *cmdList && CMD_node_get_command(*cmdList)->command != NULL; + *cmdList = CMD_node_next(*cmdList)) { - WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR)); - return; + for_control_append_set(for_ctrl, CMD_node_get_command(*cmdList)->command); }
- /* Set up the list of directories to recurse if we are going to */ - } else if (doRecurse) { - /* Allocate memory, add to list */ - dirsToWalk = WCMD_dir_stack_create(optionsRoot, NULL); - TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName)); - } - - /* Variable should follow */ - lstrcpyW(variable, thisArg); - WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable)); - varidx = for_var_char_to_index(variable[1]); - - /* Ensure line continues with IN */ - thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE); - if (!thisArg - || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, - thisArg, in_len, L"in", in_len) == CSTR_EQUAL)) { - WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR)); - return; - } - - /* Save away where the set of data starts and the variable */ - thisDepth = CMD_node_get_depth(*cmdList); - *cmdList = CMD_node_next(*cmdList); - setStart = (*cmdList); - - /* Skip until the close bracket */ - WINE_TRACE("Searching %p as the set\n", *cmdList); - while (*cmdList && - CMD_node_get_command(*cmdList)->command != NULL && - CMD_node_get_depth(*cmdList) > thisDepth) { - WINE_TRACE("Skipping %p which is part of the set\n", *cmdList); + /* step over terminating NULL CMD_NODE of set */ *cmdList = CMD_node_next(*cmdList); - } - - /* Skip the close bracket, if there is one */ - if (*cmdList) *cmdList = CMD_node_next(*cmdList); - - /* Syntax error if missing close bracket, or nothing following it - and once we have the complete set, we expect a DO */ - WINE_TRACE("Looking for 'do ' in %p\n", *cmdList); - if ((*cmdList == NULL) || !WCMD_keyword_ws_found(L"do", CMD_node_get_command(*cmdList)->command)) { - WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR)); - return; - } - - cmdEnd = *cmdList; - - /* Loop repeatedly per-directory we are potentially walking, when in for /r - mode, or once for the rest of the time. */ - do { - - /* Save away the starting position for the commands (and offset for the - first one) */ - cmdStart = *cmdList; - firstCmd = CMD_node_get_command(*cmdList)->command + 3; /* Skip 'do ' */ - itemNum = 0; - - /* If we are recursing directories (ie /R), add all sub directories now, then - prefix the root when searching for the item */ - if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk); - - thisSet = setStart; - /* Loop through all set entries */ - while (thisSet && - CMD_node_get_command(thisSet)->command != NULL && - CMD_node_get_depth(thisSet) >= thisDepth) { - - /* Loop through all entries on the same line */ - WCHAR *staticitem; - WCHAR *itemStart; - WCHAR buffer[MAXSTRING]; - - WINE_TRACE("Processing for set %p\n", thisSet); - i = 0; - while (*(staticitem = WCMD_parameter (CMD_node_get_command(thisSet)->command, i, &itemStart, TRUE, FALSE))) { - - /* - * If the parameter within the set has a wildcard then search for matching files - * otherwise do a literal substitution. - */ - - /* Take a copy of the item returned from WCMD_parameter as it is held in a - static buffer which can be overwritten during parsing of the for body */ - WCHAR item[MAXSTRING]; - lstrcpyW(item, staticitem); - - thisCmdStart = cmdStart; - - itemNum++; - WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item)); - - if (!useNumbers && !doFileset) { - WCHAR fullitem[MAX_PATH]; - int prefixlen = 0; - - /* Now build the item to use / search for in the specified directory, - as it is fully qualified in the /R case */ - if (dirsToWalk) { - lstrcpyW(fullitem, dirsToWalk->dirName); - lstrcatW(fullitem, L"\"); - lstrcatW(fullitem, item); - } else { - WCHAR *prefix = wcsrchr(item, '\'); - if (prefix) prefixlen = (prefix - item) + 1; - lstrcpyW(fullitem, item); - } - - if (wcspbrk(fullitem, L"*?")) { - hff = FindFirstFileW(fullitem, &fd); - if (hff != INVALID_HANDLE_VALUE) { - do { - BOOL isDirectory = FALSE; - - if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE; - - /* Handle as files or dirs appropriately, but ignore . and .. */ - if (isDirectory == expandDirs && - (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0)) - { - thisCmdStart = cmdStart; - WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName)); - - if (doRecurse) { - if (wcslen(dirsToWalk->dirName) + 1 + wcslen(fd.cFileName) >= MAX_PATH) - { - WINE_TRACE("Skipping too long path %s\%s\n", - debugstr_w(dirsToWalk->dirName), debugstr_w(fd.cFileName)); - continue; - } - lstrcpyW(fullitem, dirsToWalk->dirName); - lstrcatW(fullitem, L"\"); - lstrcatW(fullitem, fd.cFileName); - } else { - if (prefixlen) lstrcpynW(fullitem, item, prefixlen + 1); - fullitem[prefixlen] = 0x00; - lstrcatW(fullitem, fd.cFileName); - } - doExecuted = TRUE; - - WCMD_save_for_loop_context(FALSE); - /* Save away any existing for variable context (e.g. nested for loops) - and restore it after executing the body of this for loop */ - if (varidx >= 0) - WCMD_set_for_loop_variable(varidx, fullitem); - WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE); - WCMD_restore_for_loop_context(); - - cmdEnd = thisCmdStart; - } - } while (FindNextFileW(hff, &fd) != 0); - FindClose (hff); - } - } else { - doExecuted = TRUE; - - WCMD_save_for_loop_context(FALSE); - /* Save away any existing for variable context (e.g. nested for loops) - and restore it after executing the body of this for loop */ - if (varidx >= 0) - WCMD_set_for_loop_variable(varidx, fullitem); - WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE); - WCMD_restore_for_loop_context(); - - cmdEnd = thisCmdStart; - } - - } else if (useNumbers) { - /* Convert the first 3 numbers to signed longs and save */ - if (itemNum <=3) numbers[itemNum-1] = wcstol(item, NULL, 10); - /* else ignore them! */ - - /* Filesets - either a list of files, or a command to run and parse the output */ - } else if (doFileset && ((!forf_usebackq && *itemStart != '"') || - (forf_usebackq && *itemStart != '''))) { - - FILE *input; - WCHAR *itemparm; - - WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum, - wine_dbgstr_w(item)); - - /* If backquote or single quote, we need to launch that command - and parse the results - use a temporary file */ - if ((forf_usebackq && *itemStart == '`') || - (!forf_usebackq && *itemStart == ''')) { - - /* Use itemstart because the command is the whole set, not just the first token */ - itemparm = itemStart; - } else { - - /* Use item because the file to process is just the first item in the set */ - itemparm = item; - } - input = WCMD_forf_getinput(forf_usebackq, itemparm, (itemparm==itemStart)); - - /* Process the input file */ - if (!input) { - WCMD_print_error (); - WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item); - errorlevel = 1; - return; /* FOR loop aborts at first failure here */ - - } else { - - /* Read line by line until end of file */ - while (fgetws(buffer, ARRAY_SIZE(buffer), input)) { - size_t 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'; - WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, for_var_char_to_index(variable[1]), buffer, &doExecuted, - &forf_skip, forf_eol, forf_delims, forf_tokens); - buffer[0] = 0; - } - fclose (input); - } - - /* When we have processed the item as a whole command, abort future set processing */ - if (itemparm==itemStart) { - thisSet = NULL; - break; - } - - /* Filesets - A string literal */ - } else if (doFileset && ((!forf_usebackq && *itemStart == '"') || - (forf_usebackq && *itemStart == '''))) { - - /* Remove leading and trailing character, ready to parse with delims= delimiters - Note that the last quote is removed from the set and the string terminates - there to mimic windows */ - WCHAR *strend = wcsrchr(itemStart, forf_usebackq?''':'"'); - if (strend) { - *strend = 0x00; - itemStart++; - } - - /* Copy the item away from the global buffer used by WCMD_parameter */ - lstrcpyW(buffer, itemStart); - WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, for_var_char_to_index(variable[1]), buffer, &doExecuted, - &forf_skip, forf_eol, forf_delims, forf_tokens); - - /* Only one string can be supplied in the whole set, abort future set processing */ - thisSet = NULL; - break; - } - - WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd); - i++; - } - - /* Move onto the next set line */ - if (thisSet) thisSet = CMD_node_next(thisSet); - } - - /* If /L is provided, now run the for loop */ - if (useNumbers) { - WCHAR thisNum[20]; - - WINE_TRACE("FOR /L provided range from %ld to %ld step %ld\n", - numbers[0], numbers[2], numbers[1]); - for (i=numbers[0]; - (numbers[1]<0)? i>=numbers[2] : i<=numbers[2]; - i=i + numbers[1]) { - - swprintf(thisNum, ARRAY_SIZE(thisNum), L"%d", i); - WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum)); - - thisCmdStart = cmdStart; - doExecuted = TRUE; - - /* Save away any existing for variable context (e.g. nested for loops) - and restore it after executing the body of this for loop */ - if (varidx >= 0) - { - WCMD_save_for_loop_context(FALSE); - WCMD_set_for_loop_variable(varidx, thisNum); - } - WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE); - if (varidx >= 0) - WCMD_restore_for_loop_context(); - } - cmdEnd = thisCmdStart; - } - - /* If we are walking directories, move on to any which remain */ - if (dirsToWalk != NULL) { - dirsToWalk = WCMD_dir_stack_free(dirsToWalk); - if (dirsToWalk) WINE_TRACE("Moving to next directory to iterate: %s\n", - wine_dbgstr_w(dirsToWalk->dirName)); - else WINE_TRACE("Finished all directories.\n"); - } - - } while (dirsToWalk != NULL); - - /* Now skip over the do part if we did not perform the for loop so far. - We store in cmdEnd the next command after the do block, but we only - know this if something was run. If it has not been, we need to calculate - it. */ - if (!doExecuted) { - thisCmdStart = cmdStart; - WINE_TRACE("Skipping for loop commands due to no valid iterations\n"); - WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE); - cmdEnd = thisCmdStart; - }
- /* When the loop ends, either something like a GOTO or EXIT /b has terminated - all processing, OR it should be pointing to the end of && processing OR - it should be pointing at the NULL end of bracket for the DO. The return - value needs to be the NEXT command to execute, which it either is, or - we need to step over the closing bracket */ - *cmdList = cmdEnd; - if (cmdEnd && CMD_node_get_command(cmdEnd)->command == NULL) *cmdList = CMD_node_next(cmdEnd); + for_control_execute(for_ctrl, cmdList); + for_control_dispose(for_ctrl); }
/************************************************************************** diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index 2ef2d495a63..59ae38a528d 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -757,11 +757,10 @@ 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 +set WINE_FOO=1 2 4 +for /l %%i in (!WINE_FOO!) do echo %%i +set WINE_FOO=4 +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..fff3bbff894 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -610,8 +610,12 @@ N '' '.eh'@or_broken@'' "foo bar" -@todo_wine@foo -@todo_wine@bar +foo +bar +1 +3 +1 +3 --- in digit variables a %1 %2 b %1 %2 @@ -1014,21 +1018,21 @@ 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 -@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" @@ -1385,7 +1389,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 dde5d1e12ec..92b011db4a1 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -83,6 +83,31 @@ typedef struct _CMD_IF_CONDITION }; } CMD_IF_CONDITION;
+#define CMD_FOR_FLAG_TREE_RECURSE (1u << 0) +#define CMD_FOR_FLAG_TREE_INCLUDE_FILES (1u << 1) +#define CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES (1u << 2) + +typedef struct _CMD_FOR_CONTROL +{ + enum for_control_operator {CMD_FOR_FILETREE, + CMD_FOR_FILE_SET /* /F */, CMD_FOR_NUMBERS /* /L */} operator; + unsigned flags; /* |-ed CMD_FOR_FLAG_* */ + int variable_index; + const WCHAR *set; + union + { + const WCHAR *root_dir; /* for CMD_FOR_FILETREE */ + 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 { WCHAR *command; /* Command string to execute */ @@ -129,6 +154,25 @@ 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); +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); +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); +struct _DIRECTORY_STACK; +void WCMD_add_dirstowalk(struct _DIRECTORY_STACK *dirsToWalk); +struct _DIRECTORY_STACK *WCMD_dir_stack_create(const WCHAR *dir, const WCHAR *file); +struct _DIRECTORY_STACK *WCMD_dir_stack_free(struct _DIRECTORY_STACK *dir); + 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..6ceb1ff509a 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -991,6 +991,109 @@ 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) + { + case CMD_FOR_FILE_SET: + free((void*)for_ctrl->delims); + free((void*)for_ctrl->tokens); + break; + case CMD_FOR_FILETREE: + free((void*)for_ctrl->root_dir); + break; + default: + break; + } +} + +const char *debugstr_for_control(const CMD_FOR_CONTROL *for_ctrl) +{ + static const char* for_ctrl_strings[] = {"set", "file-set", "recurse", "directories", "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); + } + + if (for_ctrl->flags) + flags = wine_dbg_sprintf("flags=%s%s%s ", + (for_ctrl->flags & CMD_FOR_FLAG_TREE_RECURSE) ? "~recurse" : "", + (for_ctrl->flags & CMD_FOR_FLAG_TREE_INCLUDE_FILES) ? "~+files" : "", + (for_ctrl->flags & CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES) ? "~+dirs" : ""); + else + flags = ""; + switch (for_ctrl->operator) + { + case CMD_FOR_FILETREE: + options = wine_dbg_sprintf("root=(%ls) ", for_ctrl->root_dir); + break; + 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; + } + 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) + { + case CMD_FOR_FILETREE: + for_ctrl->root_dir = options && *options ? xstrdupW(options) : NULL; + break; + default: + break; + } +} + +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) + { + 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 +2196,187 @@ 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; + 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' ': + for_op = CMD_FOR_FILETREE; + flags = CMD_FOR_FLAG_TREE_INCLUDE_FILES; + break; + case L'D': + for_op = CMD_FOR_FILETREE; + flags = CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES; + break; + case L'X': + for_op = CMD_FOR_FILETREE; + flags = CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES | CMD_FOR_FLAG_TREE_RECURSE; + break; + case L'R': + for_op = CMD_FOR_FILETREE; + flags = CMD_FOR_FLAG_TREE_INCLUDE_FILES | /*CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES | */CMD_FOR_FLAG_TREE_RECURSE; + break; + case L'L': + for_op = CMD_FOR_NUMBERS; + break; + case L'F': + for_op = CMD_FOR_FILE_SET; + break; + default: + FIXME("Unexpected situation\n"); + return NULL; + } + + if (mode == L'F' || mode == L'R') + { + /* 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)); + 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)); + return NULL; +} + /*************************************************************************** * WCMD_ReadAndParseLine * @@ -2710,6 +2994,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)); @@ -2748,6 +3123,288 @@ 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 set[MAXSTRING]; + WCHAR *args; + size_t len; + CMD_NODE *body = NULL; + FILE *input; + int i; + + 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)) + { + 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(set, 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_set(CMD_FOR_CONTROL *for_ctrl, const WCHAR *from_dir, size_t ref_len, CMD_NODE *cmdList) +{ + CMD_NODE *body = NULL; + size_t len; + WCHAR set[MAXSTRING]; + WCHAR buffer[MAX_PATH]; + int i; + + if (from_dir) + { + len = wcslen(from_dir) + 1; + if (len >= ARRAY_SIZE(buffer)) return NULL; + wcscpy(buffer, from_dir); + wcscat(buffer, L"\"); + } + else + len = 0; + + wcscpy(set, for_ctrl->set); + handleExpansion(set, context != NULL, delayedsubst); + for (i = 0; ; i++) + { + WCHAR *element = WCMD_parameter(set, i, NULL, TRUE, FALSE); + if (!element || !*element) break; + if (len + wcslen(element) + 1 >= ARRAY_SIZE(buffer)) continue; + + wcscpy(&buffer[len], element); + + TRACE("Doing set element %ls\n", buffer); + + if (wcspbrk(element, L"?*")) + { + WIN32_FIND_DATAW fd; + HANDLE hff = FindFirstFileW(buffer, &fd); + size_t insert_pos = (wcsrchr(buffer, L'\') ? wcsrchr(buffer, L'\') + 1 - buffer : 0); + + if (hff == INVALID_HANDLE_VALUE) + { + TRACE("Couldn't FindFirstFile on %ls\n", buffer); + continue; + } + do + { + TRACE("Considering %ls\n", fd.cFileName); + if (!lstrcmpW(fd.cFileName, L"..") || !lstrcmpW(fd.cFileName, L".")) continue; + if (!(for_ctrl->flags & CMD_FOR_FLAG_TREE_INCLUDE_FILES) && + !(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + continue; + if (!(for_ctrl->flags & CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES) && + (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + continue; + + if (insert_pos + wcslen(fd.cFileName) + 1 >= ARRAY_SIZE(buffer)) continue; + wcscpy(&buffer[insert_pos], fd.cFileName); + + body = cmdList; + WCMD_set_for_loop_variable(for_ctrl->variable_index, buffer); + WCMD_part_execute(&body, CMD_node_get_command(body)->command + 3, FALSE, TRUE); + } while (FindNextFileW(hff, &fd) != 0); + FindClose(hff); + } + else + { + body = cmdList; + WCMD_set_for_loop_variable(for_ctrl->variable_index, buffer); + WCMD_part_execute(&body, CMD_node_get_command(body)->command + 3, FALSE, TRUE); + } + } + return body; +} + +static CMD_NODE *for_control_execute_walk_files(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *cmdList) +{ + DIRECTORY_STACK *dirs_to_walk; + size_t ref_len; + CMD_NODE *body = NULL; + + if (for_ctrl->root_dir) + { + WCHAR buffer[MAXSTRING]; + + wcscpy(buffer, for_ctrl->root_dir); + handleExpansion(buffer, context != NULL, delayedsubst); + dirs_to_walk = WCMD_dir_stack_create(buffer, NULL); + } + else dirs_to_walk = WCMD_dir_stack_create(NULL, NULL); + ref_len = wcslen(dirs_to_walk->dirName); + + while (dirs_to_walk) + { + TRACE("About to walk %p %ls for %s\n", dirs_to_walk, dirs_to_walk->dirName, debugstr_for_control(for_ctrl)); + if (for_ctrl->flags & CMD_FOR_FLAG_TREE_RECURSE) + WCMD_add_dirstowalk(dirs_to_walk); + + body = for_control_execute_set(for_ctrl, dirs_to_walk->dirName, ref_len, cmdList); + /* If we are walking directories, move on to any which remain */ + dirs_to_walk = WCMD_dir_stack_free(dirs_to_walk); + } + TRACE("Finished all directories.\n"); + + return body; +} + +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); + handleExpansion(set, context != NULL, delayedsubst); + + /* 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_FILETREE: + if (for_ctrl->flags & CMD_FOR_FLAG_TREE_RECURSE) + last = for_control_execute_walk_files(for_ctrl, *cmdList); + else + last = for_control_execute_set(for_ctrl, NULL, 0, *cmdList); + break; + 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; + default: + last = NULL; + break; + } + WCMD_restore_for_loop_context(); + *cmdList = last; +} + /*************************************************************************** * WCMD_process_commands *
@julliard any update on this MR?
This will need to be split, it's not possible to review as is.