[cmd] Add beginnings of support for delayed expansion

Ann and Jason Edmeades jason at edmeades.me.uk
Sun Jan 6 17:13:29 CST 2013


Add support for the !var! syntax when delayed expansion is asked
for. This is not complete support, but it handles the majority
of what it needs to.

(Note after this patch, I am not aware of any chunk of cmd.exe
functionality which is missing. I know of bugs, todos, untidy code,
and edge cases which do not work but the functionality is there)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.winehq.org/pipermail/wine-patches/attachments/20130106/4b67e1e0/attachment-0001.html>
-------------- next part --------------
From 662ef032b16617b8baf3194e0718c96b0cd5e342 Mon Sep 17 00:00:00 2001
From: Jason Edmeades <jason at edmeades.me.uk>
Date: Sun, 6 Jan 2013 23:06:28 +0000
Subject: [cmd] Add beginnings of support for delayed expansion

Add support for the !var! syntax when delayed expansion is asked
for. This is not complete support, but it handles the majority
of what it needs to.

(Note after this patch, I am not aware of any chunk of cmd.exe
functionality which is missing. I know of bugs, todos, untidy code,
and edge cases which do not work but the functionality is there)
---
 programs/cmd/batch.c                     |    4 +-
 programs/cmd/builtins.c                  |   22 +++++++++
 programs/cmd/tests/test_builtins.cmd.exp |    6 +--
 programs/cmd/tests/test_cmdline.cmd.exp  |    2 +-
 programs/cmd/wcmd.h                      |    4 +-
 programs/cmd/wcmdmain.c                  |   79 +++++++++++++++++++++---------
 6 files changed, 88 insertions(+), 29 deletions(-)

diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c
index 8cd48e8..09081f4 100644
--- a/programs/cmd/batch.c
+++ b/programs/cmd/batch.c
@@ -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;
 
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c
index 7aba90c..47a1fca 100644
--- a/programs/cmd/builtins.c
+++ b/programs/cmd/builtins.c
@@ -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, '=');
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp
index b35692b..ebfe8cd 100644
--- a/programs/cmd/tests/test_builtins.cmd.exp
+++ b/programs/cmd/tests/test_builtins.cmd.exp
@@ -354,16 +354,16 @@ foo
 foo
 --- runtime (delayed) expansion mode
 foo
- at todo_wine@foo at or_broken@!WINE_FOO!
+foo at or_broken@!WINE_FOO!
 foo
- at todo_wine@bar at or_broken@foo
+bar at or_broken@foo
 0
 0 at or_broken@1
 foo
 !WINE_FOO!
 --- using /V cmd flag
 foo
- at todo_wine@foo at or_broken@!WINE_FOO!
+foo at or_broken@!WINE_FOO!
 foo
 !WINE_FOO!
 ------------ Testing conditional execution ------------
diff --git a/programs/cmd/tests/test_cmdline.cmd.exp b/programs/cmd/tests/test_cmdline.cmd.exp
index 99f8c45..38e17a5 100644
--- a/programs/cmd/tests/test_cmdline.cmd.exp
+++ b/programs/cmd/tests/test_cmdline.cmd.exp
@@ -102,7 +102,7 @@ THIS FAILS: cmd ignoreme/c say one
 0 at space@
 0 at space@
 !@space@
- at todo_wine@0 at space@@or_broken@!@space@
+0 at space@@or_broken@!@space@
 @todo_wine at 0@space@
 '@space@
 + at space@
diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h
index 278a578..418f8c9 100644
--- a/programs/cmd/wcmd.h
+++ b/programs/cmd/wcmd.h
@@ -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 */
 
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c
index d2884c9..1145088 100644
--- a/programs/cmd/wcmdmain.c
+++ b/programs/cmd/wcmdmain.c
@@ -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 */
-- 
1.7.9.5


More information about the wine-patches mailing list