Commit 0b00b717 authored by Jason Edmeades's avatar Jason Edmeades Committed by Alexandre Julliard

cmd: Add beginnings of support for delayed expansion.

parent 3735bf6a
...@@ -386,7 +386,7 @@ void WCMD_splitpath(const WCHAR* path, WCHAR* drv, WCHAR* dir, WCHAR* name, WCHA ...@@ -386,7 +386,7 @@ void WCMD_splitpath(const WCHAR* path, WCHAR* drv, WCHAR* dir, WCHAR* name, WCHA
* Hence search forwards until find an invalid modifier, and then * Hence search forwards until find an invalid modifier, and then
* backwards until find for variable or 0-9 * backwards until find for variable or 0-9
*/ */
void WCMD_HandleTildaModifiers(WCHAR **start, BOOL justFors) void WCMD_HandleTildaModifiers(WCHAR **start, BOOL atExecute)
{ {
#define NUMMODIFIERS 11 #define NUMMODIFIERS 11
...@@ -444,7 +444,7 @@ void WCMD_HandleTildaModifiers(WCHAR **start, BOOL justFors) ...@@ -444,7 +444,7 @@ void WCMD_HandleTildaModifiers(WCHAR **start, BOOL justFors)
WINE_TRACE("Looking backwards for parameter id: %s\n", WINE_TRACE("Looking backwards for parameter id: %s\n",
wine_dbgstr_w(lastModifier)); wine_dbgstr_w(lastModifier));
if (!justFors && context && (*lastModifier >= '0' && *lastModifier <= '9')) { if (!atExecute && context && (*lastModifier >= '0' && *lastModifier <= '9')) {
/* Its a valid parameter identifier - OK */ /* Its a valid parameter identifier - OK */
break; break;
......
...@@ -3150,12 +3150,30 @@ void WCMD_setlocal (const WCHAR *s) { ...@@ -3150,12 +3150,30 @@ void WCMD_setlocal (const WCHAR *s) {
WCHAR *env; WCHAR *env;
struct env_stack *env_copy; struct env_stack *env_copy;
WCHAR cwd[MAX_PATH]; WCHAR cwd[MAX_PATH];
BOOL newdelay;
static const WCHAR ondelayW[] = {'E','N','A','B','L','E','D','E','L','A',
'Y','E','D','E','X','P','A','N','S','I',
'O','N','\0'};
static const WCHAR offdelayW[] = {'D','I','S','A','B','L','E','D','E','L',
'A','Y','E','D','E','X','P','A','N','S',
'I','O','N','\0'};
/* setlocal does nothing outside of batch programs */ /* setlocal does nothing outside of batch programs */
if (!context) return; if (!context) return;
/* DISABLEEXTENSIONS ignored */ /* DISABLEEXTENSIONS ignored */
/* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
(if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
if (!strcmpiW(param1, ondelayW) || !strcmpiW(param2, ondelayW)) {
newdelay = TRUE;
} else if (!strcmpiW(param1, offdelayW) || !strcmpiW(param2, offdelayW)) {
newdelay = FALSE;
} else {
newdelay = delayedsubst;
}
WINE_TRACE("Setting delayed expansion to %d\n", newdelay);
env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack)); env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
if( !env_copy ) if( !env_copy )
{ {
...@@ -3169,6 +3187,8 @@ void WCMD_setlocal (const WCHAR *s) { ...@@ -3169,6 +3187,8 @@ void WCMD_setlocal (const WCHAR *s) {
{ {
env_copy->batchhandle = context->h; env_copy->batchhandle = context->h;
env_copy->next = saved_environment; env_copy->next = saved_environment;
env_copy->delayedsubst = delayedsubst;
delayedsubst = newdelay;
saved_environment = env_copy; saved_environment = env_copy;
/* Save the current drive letter */ /* Save the current drive letter */
...@@ -3226,6 +3246,8 @@ void WCMD_endlocal (void) { ...@@ -3226,6 +3246,8 @@ void WCMD_endlocal (void) {
/* restore old environment */ /* restore old environment */
env = temp->strings; env = temp->strings;
len = 0; len = 0;
delayedsubst = temp->delayedsubst;
WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
while (env[len]) { while (env[len]) {
n = strlenW(&env[len]) + 1; n = strlenW(&env[len]) + 1;
p = strchrW(&env[len] + 1, '='); p = strchrW(&env[len] + 1, '=');
......
...@@ -354,16 +354,16 @@ foo ...@@ -354,16 +354,16 @@ foo
foo foo
--- runtime (delayed) expansion mode --- runtime (delayed) expansion mode
foo foo
@todo_wine@foo@or_broken@!WINE_FOO! foo@or_broken@!WINE_FOO!
foo foo
@todo_wine@bar@or_broken@foo bar@or_broken@foo
0 0
0@or_broken@1 0@or_broken@1
foo foo
!WINE_FOO! !WINE_FOO!
--- using /V cmd flag --- using /V cmd flag
foo foo
@todo_wine@foo@or_broken@!WINE_FOO! foo@or_broken@!WINE_FOO!
foo foo
!WINE_FOO! !WINE_FOO!
------------ Testing conditional execution ------------ ------------ Testing conditional execution ------------
......
...@@ -102,7 +102,7 @@ THIS FAILS: cmd ignoreme/c say one ...@@ -102,7 +102,7 @@ THIS FAILS: cmd ignoreme/c say one
0@space@ 0@space@
0@space@ 0@space@
!@space@ !@space@
@todo_wine@0@space@@or_broken@!@space@ 0@space@@or_broken@!@space@
@todo_wine@0@space@ @todo_wine@0@space@
'@space@ '@space@
+@space@ +@space@
......
...@@ -112,7 +112,7 @@ WCHAR *WCMD_parameter_with_delims (WCHAR *s, int n, WCHAR **start, BOOL raw, ...@@ -112,7 +112,7 @@ WCHAR *WCMD_parameter_with_delims (WCHAR *s, int n, WCHAR **start, BOOL raw,
BOOL wholecmdline, const WCHAR *delims); BOOL wholecmdline, const WCHAR *delims);
WCHAR *WCMD_skip_leading_spaces (WCHAR *string); WCHAR *WCMD_skip_leading_spaces (WCHAR *string);
BOOL WCMD_keyword_ws_found(const WCHAR *keyword, int len, const WCHAR *ptr); BOOL WCMD_keyword_ws_found(const WCHAR *keyword, int len, const WCHAR *ptr);
void WCMD_HandleTildaModifiers(WCHAR **start, BOOL justFors); void WCMD_HandleTildaModifiers(WCHAR **start, BOOL atExecute);
void WCMD_splitpath(const WCHAR* path, WCHAR* drv, WCHAR* dir, WCHAR* name, WCHAR* ext); void WCMD_splitpath(const WCHAR* path, WCHAR* drv, WCHAR* dir, WCHAR* name, WCHAR* ext);
void WCMD_strip_quotes(WCHAR *cmd); void WCMD_strip_quotes(WCHAR *cmd);
...@@ -171,6 +171,7 @@ struct env_stack ...@@ -171,6 +171,7 @@ struct env_stack
} u; } u;
WCHAR *strings; WCHAR *strings;
HANDLE batchhandle; /* Used to ensure set/endlocals stay in scope */ HANDLE batchhandle; /* Used to ensure set/endlocals stay in scope */
BOOL delayedsubst; /* Is delayed substitution in effect */
}; };
/* Data structure to save setlocal and pushd information */ /* Data structure to save setlocal and pushd information */
...@@ -201,6 +202,7 @@ extern WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING]; ...@@ -201,6 +202,7 @@ extern WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING];
extern DWORD errorlevel; extern DWORD errorlevel;
extern BATCH_CONTEXT *context; extern BATCH_CONTEXT *context;
extern FOR_CONTEXT forloopcontext; extern FOR_CONTEXT forloopcontext;
extern BOOL delayedsubst;
#endif /* !RC_INVOKED */ #endif /* !RC_INVOKED */
......
...@@ -41,6 +41,7 @@ DWORD errorlevel; ...@@ -41,6 +41,7 @@ DWORD errorlevel;
WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING]; WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING];
BOOL interactive; 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; int defaultColor = 7;
BOOL echo_mode = TRUE; BOOL echo_mode = TRUE;
...@@ -548,7 +549,7 @@ static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar) ...@@ -548,7 +549,7 @@ static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
* *
* Expands environment variables, allowing for WCHARacter substitution * Expands environment variables, allowing for WCHARacter substitution
*/ */
static WCHAR *WCMD_expand_envvar(WCHAR *start) static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR startchar)
{ {
WCHAR *endOfVar = NULL, *s; WCHAR *endOfVar = NULL, *s;
WCHAR *colonpos = NULL; WCHAR *colonpos = NULL;
...@@ -562,11 +563,12 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start) ...@@ -562,11 +563,12 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start)
static const WCHAR Time[] = {'T','I','M','E','\0'}; static const WCHAR Time[] = {'T','I','M','E','\0'};
static const WCHAR Cd[] = {'C','D','\0'}; static const WCHAR Cd[] = {'C','D','\0'};
static const WCHAR Random[] = {'R','A','N','D','O','M','\0'}; static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
static const WCHAR Delims[] = {'%',':','\0'}; WCHAR Delims[] = {'%',':','\0'}; /* First char gets replaced appropriately */
WINE_TRACE("Expanding: %s\n", wine_dbgstr_w(start)); WINE_TRACE("Expanding: %s (%c)\n", wine_dbgstr_w(start), startchar);
/* Find the end of the environment variable, and extract name */ /* Find the end of the environment variable, and extract name */
Delims[0] = startchar;
endOfVar = strpbrkW(start+1, Delims); endOfVar = strpbrkW(start+1, Delims);
if (endOfVar == NULL || *endOfVar==' ') { if (endOfVar == NULL || *endOfVar==' ') {
...@@ -587,7 +589,7 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start) ...@@ -587,7 +589,7 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start)
/* If ':' found, process remaining up until '%' (or stop at ':' if /* If ':' found, process remaining up until '%' (or stop at ':' if
a missing '%' */ a missing '%' */
if (*endOfVar==':') { if (*endOfVar==':') {
WCHAR *endOfVar2 = strchrW(endOfVar+1, '%'); WCHAR *endOfVar2 = strchrW(endOfVar+1, startchar);
if (endOfVar2 != NULL) endOfVar = endOfVar2; if (endOfVar2 != NULL) endOfVar = endOfVar2;
} }
...@@ -598,11 +600,18 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start) ...@@ -598,11 +600,18 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start)
/* If there's complex substitution, just need %var% for now /* If there's complex substitution, just need %var% for now
to get the expanded data to play with */ to get the expanded data to play with */
if (colonpos) { if (colonpos) {
*colonpos = '%'; *colonpos = startchar;
savedchar = *(colonpos+1); savedchar = *(colonpos+1);
*(colonpos+1) = 0x00; *(colonpos+1) = 0x00;
} }
/* By now, we know the variable we want to expand but it may be
surrounded by '!' if we are in delayed expansion - if so convert
to % signs. */
if (startchar=='!') {
thisVar[0] = '%';
thisVar[(endOfVar - start)] = '%';
}
WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar)); WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
/* Expand to contents, if unchanged, return */ /* Expand to contents, if unchanged, return */
...@@ -788,8 +797,11 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start) ...@@ -788,8 +797,11 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start)
* Expand the command. Native expands lines from batch programs as they are * Expand the command. Native expands lines from batch programs as they are
* read in and not again, except for 'for' variable substitution. * read in and not again, except for 'for' variable substitution.
* eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%" * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
* atExecute is TRUE when the expansion is occuring as the command is executed
* rather than at parse time, ie delayed expansion and for loops need to be
* processed
*/ */
static void handleExpansion(WCHAR *cmd, BOOL justFors) { static void handleExpansion(WCHAR *cmd, BOOL atExecute, BOOL delayed) {
/* For commands in a context (batch program): */ /* For commands in a context (batch program): */
/* Expand environment variables in a batch file %{0-9} first */ /* Expand environment variables in a batch file %{0-9} first */
...@@ -803,6 +815,9 @@ static void handleExpansion(WCHAR *cmd, BOOL justFors) { ...@@ -803,6 +815,9 @@ static void handleExpansion(WCHAR *cmd, BOOL justFors) {
WCHAR *p = cmd; WCHAR *p = cmd;
WCHAR *t; WCHAR *t;
int i; int i;
WCHAR *delayedp = NULL;
WCHAR startchar = '%';
WCHAR *normalp;
/* Display the FOR variables in effect */ /* Display the FOR variables in effect */
for (i=0;i<52;i++) { for (i=0;i<52;i++) {
...@@ -813,14 +828,22 @@ static void handleExpansion(WCHAR *cmd, BOOL justFors) { ...@@ -813,14 +828,22 @@ static void handleExpansion(WCHAR *cmd, BOOL justFors) {
} }
} }
while ((p = strchrW(p, '%'))) { /* Find the next environment variable delimiter */
normalp = strchrW(p, '%');
if (delayed) delayedp = strchrW(p, '!');
if (!normalp) p = delayedp;
else if (!delayedp) p = normalp;
else p = min(p,delayedp);
if (p) startchar = *p;
while (p) {
WINE_TRACE("Translate command:%s %d (at: %s)\n", WINE_TRACE("Translate command:%s %d (at: %s)\n",
wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p)); wine_dbgstr_w(cmd), atExecute, wine_dbgstr_w(p));
i = *(p+1) - '0'; i = *(p+1) - '0';
/* Don't touch %% unless its in Batch */ /* Don't touch %% unless its in Batch */
if (!justFors && *(p+1) == '%') { if (!atExecute && *(p+1) == startchar) {
if (context) { if (context) {
WCMD_strsubstW(p, p+1, NULL, 0); WCMD_strsubstW(p, p+1, NULL, 0);
} }
...@@ -828,17 +851,17 @@ static void handleExpansion(WCHAR *cmd, BOOL justFors) { ...@@ -828,17 +851,17 @@ static void handleExpansion(WCHAR *cmd, BOOL justFors) {
/* Replace %~ modifications if in batch program */ /* Replace %~ modifications if in batch program */
} else if (*(p+1) == '~') { } else if (*(p+1) == '~') {
WCMD_HandleTildaModifiers(&p, justFors); WCMD_HandleTildaModifiers(&p, atExecute);
p++; p++;
/* Replace use of %0...%9 if in batch program*/ /* Replace use of %0...%9 if in batch program*/
} else if (!justFors && context && (i >= 0) && (i <= 9)) { } else if (!atExecute && context && (i >= 0) && (i <= 9) && startchar == '%') {
t = WCMD_parameter(context -> command, i + context -> shift_count[i], t = WCMD_parameter(context -> command, i + context -> shift_count[i],
NULL, TRUE, TRUE); NULL, TRUE, TRUE);
WCMD_strsubstW(p, p+2, t, -1); WCMD_strsubstW(p, p+2, t, -1);
/* Replace use of %* if in batch program*/ /* Replace use of %* if in batch program*/
} else if (!justFors && context && *(p+1)=='*') { } else if (!atExecute && context && *(p+1)=='*' && startchar == '%') {
WCHAR *startOfParms = NULL; WCHAR *startOfParms = NULL;
WCHAR *thisParm = WCMD_parameter(context -> command, 0, &startOfParms, TRUE, TRUE); WCHAR *thisParm = WCMD_parameter(context -> command, 0, &startOfParms, TRUE, TRUE);
if (startOfParms != NULL) { if (startOfParms != NULL) {
...@@ -850,17 +873,25 @@ static void handleExpansion(WCHAR *cmd, BOOL justFors) { ...@@ -850,17 +873,25 @@ static void handleExpansion(WCHAR *cmd, BOOL justFors) {
} else { } else {
int forvaridx = FOR_VAR_IDX(*(p+1)); int forvaridx = FOR_VAR_IDX(*(p+1));
if (forvaridx != -1 && forloopcontext.variable[forvaridx]) { if (startchar == '%' && forvaridx != -1 && forloopcontext.variable[forvaridx]) {
/* Replace the 2 characters, % and for variable character */ /* 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 (!justFors) { } else if (!atExecute || (atExecute && startchar == '!')) {
p = WCMD_expand_envvar(p); p = WCMD_expand_envvar(p, startchar);
/* In a FOR loop, see if this is the variable to replace */ /* In a FOR loop, see if this is the variable to replace */
} else { /* Ignore %'s on second pass of batch program */ } else { /* Ignore %'s on second pass of batch program */
p++; p++;
} }
} }
/* Find the next environment variable delimiter */
normalp = strchrW(p, '%');
if (delayed) delayedp = strchrW(p, '!');
if (!normalp) p = delayedp;
else if (!delayedp) p = normalp;
else p = min(p,delayedp);
if (p) startchar = *p;
} }
return; return;
...@@ -1303,8 +1334,8 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, ...@@ -1303,8 +1334,8 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
/* Expand variables in command line mode only (batch mode will /* Expand variables in command line mode only (batch mode will
be expanded as the line is read in, except for 'for' loops) */ be expanded as the line is read in, except for 'for' loops) */
handleExpansion(new_cmd, (context != NULL)); handleExpansion(new_cmd, (context != NULL), delayedsubst);
handleExpansion(new_redir, (context != NULL)); handleExpansion(new_redir, (context != NULL), delayedsubst);
cmd = new_cmd; cmd = new_cmd;
/* /*
...@@ -1825,7 +1856,7 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE ...@@ -1825,7 +1856,7 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE
} }
/* Replace env vars if in a batch context */ /* Replace env vars if in a batch context */
if (context) handleExpansion(extraSpace, FALSE); if (context) handleExpansion(extraSpace, FALSE, FALSE);
/* Skip preceding whitespace */ /* Skip preceding whitespace */
while (*curPos == ' ' || *curPos == '\t') curPos++; while (*curPos == ' ' || *curPos == '\t') curPos++;
...@@ -2222,7 +2253,7 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE ...@@ -2222,7 +2253,7 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE
} while (*extraData == 0x00); } while (*extraData == 0x00);
curPos = extraSpace; curPos = extraSpace;
if (context) handleExpansion(extraSpace, FALSE); if (context) handleExpansion(extraSpace, FALSE, FALSE);
/* Continue to echo commands IF echo is on and in batch program */ /* Continue to echo commands IF echo is on and in batch program */
if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) { if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
WCMD_output_asis(extraSpace); WCMD_output_asis(extraSpace);
...@@ -2313,6 +2344,7 @@ int wmain (int argc, WCHAR *argvW[]) ...@@ -2313,6 +2344,7 @@ int wmain (int argc, WCHAR *argvW[])
WCHAR envvar[4]; WCHAR envvar[4];
BOOL opt_q; BOOL opt_q;
int opt_t = 0; int opt_t = 0;
static const WCHAR offW[] = {'O','F','F','\0'};
static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'}; static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'}; static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
CMD_LIST *toExecute = NULL; /* Commands left to be executed */ CMD_LIST *toExecute = NULL; /* Commands left to be executed */
...@@ -2366,13 +2398,17 @@ int wmain (int argc, WCHAR *argvW[]) ...@@ -2366,13 +2398,17 @@ int wmain (int argc, WCHAR *argvW[])
unicodeOutput = FALSE; unicodeOutput = FALSE;
} else if (tolowerW(c)=='u') { } else if (tolowerW(c)=='u') {
unicodeOutput = TRUE; unicodeOutput = TRUE;
} else if (tolowerW(c)=='v' && argPos[2]==':') {
delayedsubst = strncmpiW(&argPos[3], offW, 3);
if (delayedsubst) WINE_TRACE("Delayed substitution is on\n");
} else if (tolowerW(c)=='t' && argPos[2]==':') { } else if (tolowerW(c)=='t' && argPos[2]==':') {
opt_t=strtoulW(&argPos[3], NULL, 16); opt_t=strtoulW(&argPos[3], NULL, 16);
} else if (tolowerW(c)=='x' || tolowerW(c)=='y') { } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
/* Ignored for compatibility with Windows */ /* Ignored for compatibility with Windows */
} }
if (argPos[2]==0 || argPos[2]==' ' || argPos[2]=='\t') { if (argPos[2]==0 || argPos[2]==' ' || argPos[2]=='\t' ||
tolowerW(c)=='v') {
args++; args++;
WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE); WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE);
} }
...@@ -2387,8 +2423,7 @@ int wmain (int argc, WCHAR *argvW[]) ...@@ -2387,8 +2423,7 @@ int wmain (int argc, WCHAR *argvW[])
} }
if (opt_q) { if (opt_q) {
static const WCHAR eoff[] = {'O','F','F','\0'}; WCMD_echo(offW);
WCMD_echo(eoff);
} }
/* Until we start to read from the keyboard, stay as non-interactive */ /* Until we start to read from the keyboard, stay as non-interactive */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment