From: Dmitry Sokolov mr.dmitry.sokolov@gmail.com
Visual Studio's native tool command prompt uses rare FOR loop variables: %%1, %%2. This fix allows to use %%1 var in FOR loops.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=55401 --- programs/cmd/batch.c | 4 ++-- programs/cmd/builtins.c | 16 ++++++++----- programs/cmd/tests/test_builtins.cmd | 14 +++++++++++ programs/cmd/tests/test_builtins.cmd.exp | 3 +++ programs/cmd/wcmd.h | 30 ++++++++++++++++++++---- programs/cmd/wcmdmain.c | 9 +++---- 6 files changed, 60 insertions(+), 16 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index 3586cd45731..7ea51c3de6b 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 = get_FOR_var_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 = get_FOR_var_index(*lastModifier); lstrcpyW(outputparam, forloopcontext.variable[foridx]); }
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index ca703af52ec..d2cb7745a3a 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -37,6 +37,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(cmd); extern int defaultColor; extern BOOL echo_mode; extern BOOL interactive; +extern void handleExpansion(WCHAR *cmd, BOOL atExecute, BOOL delayed);
struct env_stack *pushd_directories; const WCHAR inbuilt[][10] = { @@ -1979,15 +1980,18 @@ static void WCMD_parse_line(CMD_LIST *cmdStart, lasttoken = -1; nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound, &starfound, &thisduplicate); - varidx = FOR_VAR_IDX(variable); + varidx = get_FOR_var_index(variable);
/* Empty out variables */ for (varoffset=0; - varidx >= 0 && varoffset<totalfound && (((varidx%26) + varoffset) < 26); + varidx >= 0 && varoffset < totalfound && varidx+varoffset <= get_FOR_var_index_upper_boundary(varidx); varoffset++) { forloopcontext.variable[varidx + varoffset] = emptyW; }
+ /* Expand outer loop variables */ + if (wcschr(buffer, '%')) handleExpansion(buffer, (context != NULL), FALSE); + /* Loop extracting the tokens Note: nexttoken of 0 means there were no tokens requested, to handle the special case of tokens=* */ @@ -2003,7 +2007,7 @@ static void WCMD_parse_line(CMD_LIST *cmdStart, if (varidx >=0) { if (parm) forloopcontext.variable[varidx + varoffset] = xstrdupW(parm); varoffset++; - if (((varidx%26)+varoffset) >= 26) break; + if (varidx+varoffset > get_FOR_var_index_upper_boundary(varidx)) break; }
/* Find the next token */ @@ -2014,7 +2018,7 @@ static void WCMD_parse_line(CMD_LIST *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 && varidx+varoffset <= get_FOR_var_index_upper_boundary(varidx)) { nexttoken++; WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims); WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n", @@ -2215,8 +2219,8 @@ void WCMD_for (WCHAR *p, CMD_LIST **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 = get_FOR_var_index(variable[1]); + WINE_TRACE("Variable identified as %s (idx: %d)\n", wine_dbgstr_w(variable), varidx);
/* Ensure line continues with IN */ thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE); diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index 510a1ba5931..6081b945a7e 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -1604,6 +1604,20 @@ for %%i in (test) do ( ) echo d4 ) + +echo --- for loop with rare var names +set "WINE_ARGS= -foo=bar -x=y" +:test_for_loop_params_parse +for /F "tokens=1,* delims= " %%a in ("%WINE_ARGS%") do ( + for /F "tokens=1,2 delims==" %%1 in ("%%a") do ( + echo inner argument {%%1, %%2} + ) + set "WINE_ARGS=%%b" + goto :test_for_loop_params_parse +) +set "WINE_ARGS=" + + echo --- set /a goto :testseta
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 0f48a823109..5a207686ff6 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -1065,6 +1065,9 @@ d3 a4 c4 d4 +--- for loop with rare var names +inner argument {-foo, bar} +inner argument {-x, y} --- set /a ------ individual operations WINE_foo correctly 3 diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index 1935bbcdcd2..24a317878e0 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -185,12 +185,34 @@ 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) +#define MAX_FOR_VARIABLES 62 + +static inline int get_FOR_var_index(WCHAR c) +{ + if (c >= 'a' && c <= 'z') { return c - 'a'; } + if (c >= 'A' && c <= 'Z') { return 26 + c - 'A'; } + if (c >= '0' && c <= '9') { return 52 + c - '0'; } + return -1; +} + +static inline WCHAR get_FOR_var_name(int index) +{ + if (index >= 52) { return '0' + index - 52; } + if (index >= 26) { return 'A' + index - 26; } + if (index >= 0) { return 'a' + index; } + return 'a'; +} + +static inline int get_FOR_var_index_upper_boundary(int index) +{ + if (index >= 52) { return 61; } + if (index >= 26) { return 51; } + if (index >= 0) { return 25; } + return 0; +}
typedef struct _FOR_CONTEXT { - WCHAR *variable[MAX_FOR_VARIABLES]; /* a-z then A-Z */ + WCHAR *variable[MAX_FOR_VARIABLES]; /* a-z then A-Z then 0-9 */ } FOR_CONTEXT;
/* diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 843fef8ea50..f3f14a005c8 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -794,7 +794,7 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR startchar) * rather than at parse time, i.e. delayed expansion and for loops need to be * processed */ -static void handleExpansion(WCHAR *cmd, BOOL atExecute, BOOL delayed) { +void handleExpansion(WCHAR *cmd, BOOL atExecute, BOOL delayed) {
/* For commands in a context (batch program): */ /* Expand environment variables in a batch file %{0-9} first */ @@ -813,10 +813,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', + get_FOR_var_name(i), wine_dbgstr_w(forloopcontext.variable[i])); } } @@ -865,9 +865,10 @@ 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 = get_FOR_var_index(*(p+1)); if (startchar == '%' && forvaridx != -1 && forloopcontext.variable[forvaridx]) { /* Replace the 2 characters, % and for variable character */ + WINE_TRACE("FOR variable substitute %%%c with '%s'\n", *(p+1), wine_dbgstr_w(forloopcontext.variable[forvaridx])); WCMD_strsubstW(p, p + 2, forloopcontext.variable[forvaridx], -1); } else if (!atExecute || startchar == '!') { p = WCMD_expand_envvar(p, startchar);