cmd: Add beginnings of support for delayed expansion.

This commit is contained in:
Jason Edmeades 2013-01-06 23:06:28 +00:00 committed by Alexandre Julliard
parent 3735bf6af5
commit 0b00b717b9
6 changed files with 88 additions and 29 deletions

View file

@ -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
* 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
@ -444,7 +444,7 @@ void WCMD_HandleTildaModifiers(WCHAR **start, BOOL justFors)
WINE_TRACE("Looking backwards for parameter id: %s\n",
wine_dbgstr_w(lastModifier));
if (!justFors && context && (*lastModifier >= '0' && *lastModifier <= '9')) {
if (!atExecute && context && (*lastModifier >= '0' && *lastModifier <= '9')) {
/* Its a valid parameter identifier - OK */
break;

View file

@ -3150,12 +3150,30 @@ void WCMD_setlocal (const WCHAR *s) {
WCHAR *env;
struct env_stack *env_copy;
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 */
if (!context) return;
/* 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));
if( !env_copy )
{
@ -3169,6 +3187,8 @@ void WCMD_setlocal (const WCHAR *s) {
{
env_copy->batchhandle = context->h;
env_copy->next = saved_environment;
env_copy->delayedsubst = delayedsubst;
delayedsubst = newdelay;
saved_environment = env_copy;
/* Save the current drive letter */
@ -3226,6 +3246,8 @@ void WCMD_endlocal (void) {
/* restore old environment */
env = temp->strings;
len = 0;
delayedsubst = temp->delayedsubst;
WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
while (env[len]) {
n = strlenW(&env[len]) + 1;
p = strchrW(&env[len] + 1, '=');

View file

@ -354,16 +354,16 @@ foo
foo
--- runtime (delayed) expansion mode
foo
@todo_wine@foo@or_broken@!WINE_FOO!
foo@or_broken@!WINE_FOO!
foo
@todo_wine@bar@or_broken@foo
bar@or_broken@foo
0
0@or_broken@1
foo
!WINE_FOO!
--- using /V cmd flag
foo
@todo_wine@foo@or_broken@!WINE_FOO!
foo@or_broken@!WINE_FOO!
foo
!WINE_FOO!
------------ Testing conditional execution ------------

View file

@ -102,7 +102,7 @@ THIS FAILS: cmd ignoreme/c say one
0@space@
0@space@
!@space@
@todo_wine@0@space@@or_broken@!@space@
0@space@@or_broken@!@space@
@todo_wine@0@space@
'@space@
+@space@

View file

@ -112,7 +112,7 @@ WCHAR *WCMD_parameter_with_delims (WCHAR *s, int n, WCHAR **start, BOOL raw,
BOOL wholecmdline, const WCHAR *delims);
WCHAR *WCMD_skip_leading_spaces (WCHAR *string);
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_strip_quotes(WCHAR *cmd);
@ -171,6 +171,7 @@ struct env_stack
} u;
WCHAR *strings;
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 */
@ -201,6 +202,7 @@ extern WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING];
extern DWORD errorlevel;
extern BATCH_CONTEXT *context;
extern FOR_CONTEXT forloopcontext;
extern BOOL delayedsubst;
#endif /* !RC_INVOKED */

View file

@ -41,6 +41,7 @@ DWORD errorlevel;
WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING];
BOOL interactive;
FOR_CONTEXT forloopcontext; /* The 'for' loop context */
BOOL delayedsubst = FALSE; /* The current delayed substitution setting */
int defaultColor = 7;
BOOL echo_mode = TRUE;
@ -548,7 +549,7 @@ static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
*
* 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 *colonpos = NULL;
@ -562,11 +563,12 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start)
static const WCHAR Time[] = {'T','I','M','E','\0'};
static const WCHAR Cd[] = {'C','D','\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 */
Delims[0] = startchar;
endOfVar = strpbrkW(start+1, Delims);
if (endOfVar == NULL || *endOfVar==' ') {
@ -587,7 +589,7 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start)
/* If ':' found, process remaining up until '%' (or stop at ':' if
a missing '%' */
if (*endOfVar==':') {
WCHAR *endOfVar2 = strchrW(endOfVar+1, '%');
WCHAR *endOfVar2 = strchrW(endOfVar+1, startchar);
if (endOfVar2 != NULL) endOfVar = endOfVar2;
}
@ -598,11 +600,18 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start)
/* If there's complex substitution, just need %var% for now
to get the expanded data to play with */
if (colonpos) {
*colonpos = '%';
*colonpos = startchar;
savedchar = *(colonpos+1);
*(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));
/* Expand to contents, if unchanged, return */
@ -788,8 +797,11 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start)
* Expand the command. Native expands lines from batch programs as they are
* read in and not again, except for 'for' variable substitution.
* 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): */
/* Expand environment variables in a batch file %{0-9} first */
@ -803,6 +815,9 @@ static void handleExpansion(WCHAR *cmd, BOOL justFors) {
WCHAR *p = cmd;
WCHAR *t;
int i;
WCHAR *delayedp = NULL;
WCHAR startchar = '%';
WCHAR *normalp;
/* Display the FOR variables in effect */
for (i=0;i<52;i++) {
@ -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_dbgstr_w(cmd), justFors, wine_dbgstr_w(p));
wine_dbgstr_w(cmd), atExecute, wine_dbgstr_w(p));
i = *(p+1) - '0';
/* Don't touch %% unless its in Batch */
if (!justFors && *(p+1) == '%') {
if (!atExecute && *(p+1) == startchar) {
if (context) {
WCMD_strsubstW(p, p+1, NULL, 0);
}
@ -828,17 +851,17 @@ static void handleExpansion(WCHAR *cmd, BOOL justFors) {
/* Replace %~ modifications if in batch program */
} else if (*(p+1) == '~') {
WCMD_HandleTildaModifiers(&p, justFors);
WCMD_HandleTildaModifiers(&p, atExecute);
p++;
/* 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],
NULL, TRUE, TRUE);
WCMD_strsubstW(p, p+2, t, -1);
/* Replace use of %* if in batch program*/
} else if (!justFors && context && *(p+1)=='*') {
} else if (!atExecute && context && *(p+1)=='*' && startchar == '%') {
WCHAR *startOfParms = NULL;
WCHAR *thisParm = WCMD_parameter(context -> command, 0, &startOfParms, TRUE, TRUE);
if (startOfParms != NULL) {
@ -850,17 +873,25 @@ static void handleExpansion(WCHAR *cmd, BOOL justFors) {
} else {
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 */
WCMD_strsubstW(p, p + 2, forloopcontext.variable[forvaridx], -1);
} else if (!justFors) {
p = WCMD_expand_envvar(p);
} else if (!atExecute || (atExecute && startchar == '!')) {
p = WCMD_expand_envvar(p, startchar);
/* In a FOR loop, see if this is the variable to replace */
} else { /* Ignore %'s on second pass of batch program */
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;
@ -1303,8 +1334,8 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
/* Expand variables in command line mode only (batch mode will
be expanded as the line is read in, except for 'for' loops) */
handleExpansion(new_cmd, (context != NULL));
handleExpansion(new_redir, (context != NULL));
handleExpansion(new_cmd, (context != NULL), delayedsubst);
handleExpansion(new_redir, (context != NULL), delayedsubst);
cmd = new_cmd;
/*
@ -1825,7 +1856,7 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE
}
/* Replace env vars if in a batch context */
if (context) handleExpansion(extraSpace, FALSE);
if (context) handleExpansion(extraSpace, FALSE, FALSE);
/* Skip preceding whitespace */
while (*curPos == ' ' || *curPos == '\t') curPos++;
@ -2222,7 +2253,7 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE
} while (*extraData == 0x00);
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 */
if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
WCMD_output_asis(extraSpace);
@ -2313,6 +2344,7 @@ int wmain (int argc, WCHAR *argvW[])
WCHAR envvar[4];
BOOL opt_q;
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 defaultpromptW[] = {'$','P','$','G','\0'};
CMD_LIST *toExecute = NULL; /* Commands left to be executed */
@ -2366,13 +2398,17 @@ int wmain (int argc, WCHAR *argvW[])
unicodeOutput = FALSE;
} else if (tolowerW(c)=='u') {
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]==':') {
opt_t=strtoulW(&argPos[3], NULL, 16);
} else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
/* 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++;
WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE);
}
@ -2387,8 +2423,7 @@ int wmain (int argc, WCHAR *argvW[])
}
if (opt_q) {
static const WCHAR eoff[] = {'O','F','F','\0'};
WCMD_echo(eoff);
WCMD_echo(offW);
}
/* Until we start to read from the keyboard, stay as non-interactive */