[PATCH 1/4] [cmd] Add support for tokens= (for /f)
Ann and Jason Edmeades
jason at edmeades.me.uk
Mon Nov 19 15:02:33 CST 2012
This adds support for the last piece of functionality I am aware
is missing from the for loop processing.
[bug 21047]
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.winehq.org/pipermail/wine-patches/attachments/20121119/e45019ff/attachment.html>
-------------- next part --------------
From a9f5427306a219ad527e2554e084280091c37090 Mon Sep 17 00:00:00 2001
From: Jason Edmeades <jason at edmeades.me.uk>
Date: Thu, 8 Nov 2012 21:53:08 +0000
Subject: [PATCH 1/4] [cmd] Add support for tokens= (for /f)
This adds support for the last piece of functionality I am aware
is missing from the for loop processing.
[bug 21047]
---
programs/cmd/builtins.c | 200 +++++++++++++++++++++++++++---
programs/cmd/tests/test_builtins.cmd | 31 +++++
programs/cmd/tests/test_builtins.cmd.exp | 21 ++++
3 files changed, 237 insertions(+), 15 deletions(-)
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c
index b323668..2e7fdd9 100644
--- a/programs/cmd/builtins.c
+++ b/programs/cmd/builtins.c
@@ -1609,7 +1609,7 @@ static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
pos++;
}
tokens[i++] = 0; /* Null terminate the tokens */
- WINE_FIXME("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
+ WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
} else {
WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
@@ -1670,6 +1670,111 @@ static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
}
/**************************************************************************
+ * WCMD_for_nexttoken
+ *
+ * Parse the token= line, identifying the next highest number not processed
+ * so far. Count how many tokens are referred (including duplicates) and
+ * optionally return that, plus optionally indicate if the tokens= line
+ * ends in a star.
+ *
+ * Parameters:
+ * lasttoken [I] - Identifies the token index of the last one
+ * returned so far (-1 used for first loop)
+ * tokenstr [I] - The specified tokens= line
+ * firstCmd [O] - Optionally indicate how many tokens are listed
+ * doAll [O] - Optionally indicate if line ends with *
+ * duplicates [O] - Optionally indicate if there is any evidence of
+ * overlaying tokens in the string
+ * Note the caller should keep a running track of duplicates as the tokens
+ * are recursively passed. If any have duplicates, then the * token should
+ * not be honoured.
+ */
+static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr,
+ int *totalfound, BOOL *doall,
+ BOOL *duplicates)
+{
+ WCHAR *pos = tokenstr;
+ int nexttoken = -1;
+
+ if (totalfound) *totalfound = 0;
+ if (doall) *doall = FALSE;
+ if (duplicates) *duplicates = FALSE;
+
+ WINE_TRACE("Find next token after %d in %s was %d\n", lasttoken,
+ wine_dbgstr_w(tokenstr), nexttoken);
+
+ /* Loop through the token string, parsing it. Valid syntax is:
+ token=m or x-y with comma delimiter and optionally * to finish*/
+ while (*pos) {
+ int nextnumber1, nextnumber2 = -1;
+ WCHAR *nextchar;
+
+ /* Get the next number */
+ nextnumber1 = strtoulW(pos, &nextchar, 10);
+
+ /* If it is followed by a minus, its a range, so get the next one as well */
+ if (*nextchar == '-') {
+ nextnumber2 = strtoulW(nextchar+1, &nextchar, 10);
+
+ /* We want to return the lowest number that is higher than lasttoken
+ but only if range is positive */
+ if (nextnumber2 >= nextnumber1 &&
+ lasttoken < nextnumber2) {
+
+ int nextvalue;
+ if (nexttoken == -1) {
+ nextvalue = max(nextnumber1, (lasttoken+1));
+ } else {
+ nextvalue = min(nexttoken, max(nextnumber1, (lasttoken+1)));
+ }
+
+ /* Flag if duplicates identified */
+ if (nexttoken == nextvalue && duplicates) *duplicates = TRUE;
+
+ nexttoken = nextvalue;
+ }
+
+ /* Update the running total for the whole range */
+ if (nextnumber2 >= nextnumber1 && totalfound) {
+ *totalfound = *totalfound + 1 + (nextnumber2 - nextnumber1);
+ }
+
+ } else {
+ if (totalfound) (*totalfound)++;
+
+ /* See if the number found is one we have already seen */
+ if (nextnumber1 == nexttoken && duplicates) *duplicates = TRUE;
+
+ /* We want to return the lowest number that is higher than lasttoken */
+ if (lasttoken < nextnumber1 &&
+ ((nexttoken == -1) || (nextnumber1 < nexttoken))) {
+ nexttoken = nextnumber1;
+ }
+
+ }
+
+ /* Remember if it is followed by a star, and if it is indicate a need to
+ show all tokens, unless a duplicate has been found */
+ if (*nextchar == '*') {
+ if (doall) *doall = TRUE;
+ if (totalfound) (*totalfound)++;
+ }
+
+ /* Step on to the next character */
+ pos = nextchar;
+ if (*pos) pos++;
+ }
+
+ /* Return result */
+ if (nexttoken == -1) nexttoken = lasttoken;
+ WINE_TRACE("Found next token after %d was %d\n", lasttoken, nexttoken);
+ if (totalfound) WINE_TRACE("Found total tokens in total %d\n", *totalfound);
+ if (doall && *doall) WINE_TRACE("Request for all tokens found\n");
+ if (duplicates && *duplicates) WINE_TRACE("Duplicate numbers found\n");
+ return nexttoken;
+}
+
+/**************************************************************************
* WCMD_parse_line
*
* When parsing file or string contents (for /f), once the string to parse
@@ -1688,6 +1793,7 @@ static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
* forf_skip [I/O] - How many lines to skip first
* forf_eol [I] - The 'end of line' (comment) character
* forf_delims [I] - The delimiters to use when breaking the string apart
+ * forf_tokens [I] - The tokens to use when breaking the string apart
*/
static void WCMD_parse_line(CMD_LIST *cmdStart,
const WCHAR *firstCmd,
@@ -1697,11 +1803,17 @@ static void WCMD_parse_line(CMD_LIST *cmdStart,
BOOL *doExecuted,
int *forf_skip,
WCHAR forf_eol,
- WCHAR *forf_delims) {
+ WCHAR *forf_delims,
+ WCHAR *forf_tokens) {
- WCHAR *parm, *where;
+ WCHAR *parm;
FOR_CONTEXT oldcontext;
- int varidx;
+ int varidx, varoffset;
+ int nexttoken, lasttoken = -1;
+ BOOL starfound = FALSE;
+ BOOL thisduplicate = FALSE;
+ BOOL anyduplicates = FALSE;
+ int totalfound;
/* Skip lines if requested */
if (*forf_skip) {
@@ -1712,23 +1824,81 @@ static void WCMD_parse_line(CMD_LIST *cmdStart,
/* Save away any existing for variable context (e.g. nested for loops) */
oldcontext = forloopcontext;
- /* Extract the parameter */
- parm = WCMD_parameter_with_delims(buffer, 0, &where, FALSE, FALSE, forf_delims);
- WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
- wine_dbgstr_w(buffer));
-
- /* FIXME: Use tokens= line to populate forloopcontext */
+ /* 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).
+ Rough logic:
+ Count how many tokens are named in the line, identify the lowest
+ Empty (set to null terminated string) that number of named variables
+ While lasttoken != nextlowest
+ %letter = parameter number 'nextlowest'
+ letter++ (if >26 or >52 abort)
+ Go through token= string finding next lowest number
+ If token ends in * set %letter = raw position of token(nextnumber+1)
+ */
+ lasttoken = -1;
+ nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
+ NULL, &thisduplicate);
varidx = FOR_VAR_IDX(variable);
- if (varidx >=0) forloopcontext.variable[varidx] = heap_strdupW(parm);
- if (where && where[0] != forf_eol) {
+ /* Empty out variables */
+ for (varoffset=0;
+ varidx >= 0 && varoffset<totalfound && ((varidx+varoffset)%26);
+ varoffset++) {
+ forloopcontext.variable[varidx + varoffset] = (WCHAR *)nullW;
+ /* Stop if we walk beyond z or Z */
+ if (((varidx+varoffset) % 26) == 0) break;
+ }
+
+ /* Loop extracting the tokens */
+ varoffset = 0;
+ WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
+ while (varidx >= 0 && (nexttoken > lasttoken)) {
+ anyduplicates |= thisduplicate;
+
+ /* Extract the token number requested and set into the next variable context */
+ parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, FALSE, FALSE, forf_delims);
+ WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
+ varidx + varoffset, wine_dbgstr_w(parm));
+ if (varidx >=0) {
+ forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
+ varoffset++;
+ if (((varidx + varoffset) %26) == 0) break;
+ }
+
+ /* Find the next token */
+ lasttoken = nexttoken;
+ nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
+ &starfound, &thisduplicate);
+ }
+
+ /* 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+varoffset) % 26)) {
+ nexttoken++;
+ 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));
+ forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
+ }
+
+ /* Execute the body of the foor loop with these values */
+ if (forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) {
CMD_LIST *thisCmdStart = cmdStart;
*doExecuted = TRUE;
WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
*cmdEnd = thisCmdStart;
}
- if (varidx >=0) heap_free(forloopcontext.variable[varidx]);
+ /* 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] != nullW)) {
+ heap_free(forloopcontext.variable[i]);
+ }
+ }
+ }
/* Restore the original for variable contextx */
forloopcontext = oldcontext;
@@ -2096,7 +2266,7 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
/* Read line by line until end of file */
while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
- &forf_skip, forf_eol, forf_delims);
+ &forf_skip, forf_eol, forf_delims, forf_tokens);
buffer[0] = 0;
}
CloseHandle (input);
@@ -2124,7 +2294,7 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
/* Copy the item away from the global buffer used by WCMD_parameter */
strcpyW(buffer, itemStart);
WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
- &forf_skip, forf_eol, forf_delims);
+ &forf_skip, forf_eol, forf_delims, forf_tokens);
/* Only one string can be supplied in the whole set, abort future set processing */
thisSet = NULL;
diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd
index e7b8516..a1a3c4a 100644
--- a/programs/cmd/tests/test_builtins.cmd
+++ b/programs/cmd/tests/test_builtins.cmd
@@ -1194,6 +1194,37 @@ for /f "skip=02" %%i in (foo) do echo %%i
for /f "skip=0x2" %%i in (foo) do echo %%i
for /f "skip=1" %%i in ("skipme") do echo %%i > output_file
if not exist output_file (echo no output) else (del output_file)
+echo ------ tokens= option
+rem Basic
+for /f %%i in ("a b c d e f g") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m o=%%o
+for /f "tokens=2" %%i in ("a b c d e f g") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m o=%%o
+for /f "tokens=1,3,5-7" %%i in ("a b c d e f g") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m o=%%o
+rem Show * means the rest
+for /f "tokens=1,5*" %%i in ("a b c d e f g") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m o=%%o
+for /f "tokens=6,9*" %%i in ("a b c d e f g h i j k l m n o p q r s t u v w x y z") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m o=%%o
+rem Show * means the rest (not tokenized and rebuilt)
+for /f "tokens=6,9*" %%i in ("a b c d e f g h i j k l m n;;== o p q r s t u v w x y z") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m o=%%o
+rem Order is irrelevant
+for /f "tokens=1,2,3*" %%i in ("a b c d e f g") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m n=%%n o=%%o
+for /f "tokens=3,2,1*" %%i in ("a b c d e f g") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m n=%%n o=%%o
+rem Duplicates are ignored
+for /f "tokens=1,2,1*" %%i in ("a b c d e f g") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m n=%%n o=%%o
+rem Large tokens are allowed
+for /f "tokens=25,1,5*" %%i in ("a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m n=%%n o=%%o
+rem Show tokens blanked in advance regardless of uniqueness of requested tokens
+for /f "tokens=1,1,1,2*" %%i in ("a b c d e f g") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m n=%%n o=%%o
+for /f "tokens=1-2,1-2,1-2" %%i in ("a b c d e f g") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m n=%%n o=%%o
+rem Show No wrapping from z to A BUT wrapping sort of occurs Z to a occurs
+for /f "tokens=1-20" %%u in ("a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z") do echo u=%%u v=%%v w=%%w x=%%x y=%%y z=%%z A=%%A a=%%a
+for /f "tokens=1-20" %%U in ("a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z") do echo U=%%U V=%%V W=%%W X=%%X Y=%%Y Z=%%Z A=%%A a=%%a
+rem Show negative ranges have no effect
+for /f "tokens=1-3,5" %%i in ("a b c d e f g") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m o=%%o
+for /f "tokens=3-1,5" %%i in ("a b c d e f g") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m o=%%o
+rem Show duplicates stop * from working
+for /f "tokens=1,2,3*" %%i in ("a b c d e f g") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m n=%%n o=%%o
+for /f "tokens=1,1,3*" %%i in ("a b c d e f g") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m n=%%n o=%%o
+for /f "tokens=2,2,3*" %%i in ("a b c d e f g") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m n=%%n o=%%o
+for /f "tokens=3,2,3*" %%i in ("a b c d e f g") do echo h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m n=%%n o=%%o
cd ..
rd /s/q foobar
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp
index 1d42a96..b30698a 100644
--- a/programs/cmd/tests/test_builtins.cmd.exp
+++ b/programs/cmd/tests/test_builtins.cmd.exp
@@ -842,6 +842,27 @@ no output
c
c
no output
+------ tokens= option
+h=%h i=a j=%j k=%k l=%l m=%m o=%o
+h=%h i=b j=%j k=%k l=%l m=%m o=%o
+h=%h i=a j=c k=e l=f m=g o=%o
+h=%h i=a j=e k=f g l=%l m=%m o=%o
+h=%h i=f j=i k=j k l m n o p q r s t u v w x y z l=%l m=%m o=%o
+h=%h i=f j=i k=j k l m n;;== o p q r s t u v w x y z l=%l m=%m o=%o
+h=%h i=a j=b k=c l=d e f g m=%m n=%n o=%o
+h=%h i=a j=b k=c l=d e f g m=%m n=%n o=%o
+h=%h i=a j=b k= l= m=%m n=%n o=%o
+h=%h i=a j=e k=y l=z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z m=%m n=%n o=%o
+h=%h i=a j=b k= l= m= n=%n o=%o
+h=%h i=a j=b k= l= m= n= o=%o
+u=a v=b w=c x=d y=e z=f A=%A a=%a
+ at todo_wine@U=a V=b W=c X=d Y=e Z=f A=%A a=m
+h=%h i=a j=b k=c l=e m=%m o=%o
+h=%h i=e j=%j k=%k l=%l m=%m o=%o
+h=%h i=a j=b k=c l=d e f g m=%m n=%n o=%o
+h=%h i=a j=c k= l= m=%m n=%n o=%o
+h=%h i=b j=c k= l= m=%m n=%n o=%o
+h=%h i=b j=c k= l= m=%m n=%n o=%o
------------ Testing del /a ------------
not-r.test not found after delete, good
r.test found before delete, good
--
1.7.9.5
More information about the wine-patches
mailing list