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
-- v2: programs/cmd: Introduce helpers to handle directory walk. programs/cmd: Enable '%0' through '%9' as valid FOR loop variables. programs/cmd: Introduce helpers to save and restore FOR loop contexts. programs/cmd: Introduce helpers to handle FOR variables. programs/cmd: Add more tests about FOR loops.
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");
Pushed V2 (removing the largest change).