https://git.reactos.org/?p=reactos.git;a=commitdiff;h=71cd64d66a20e91af4cf1…
commit 71cd64d66a20e91af4cf1b702607f9ff8e79bf91
Author: Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
AuthorDate: Mon Jul 27 01:11:23 2020 +0200
Commit: Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
CommitDate: Wed Aug 19 20:36:01 2020 +0200
[CMD_ROSTEST] Add tests for GOTO label parsing.
---
modules/rostests/win32/cmd/test_goto_call.cmd | 65 +++++++++++++++++++++--
modules/rostests/win32/cmd/test_goto_call.cmd.exp | 7 +++
2 files changed, 69 insertions(+), 3 deletions(-)
diff --git a/modules/rostests/win32/cmd/test_goto_call.cmd b/modules/rostests/win32/cmd/test_goto_call.cmd
index 6a8d3579bc5..d2356a1b447 100644
--- a/modules/rostests/win32/cmd/test_goto_call.cmd
+++ b/modules/rostests/win32/cmd/test_goto_call.cmd
@@ -11,8 +11,9 @@ setlocal enabledelayedexpansion
:: GOTO/CALL jump to labels present forward to their call-point. Only when
:: the label cannot be found forward, the search is then restarted from the
:: beginning of the batch file onwards up to the original call-point.
-::
-goto :test_start
+
+:: GOTO with a label parameter without ':' works.
+goto test_start
:: Execution must never go there!
:test_goto
@@ -36,7 +37,8 @@ goto :continue
:test_goto
echo Test GOTO ok
-goto :do_test_call
+:: GOTO also understands '+' instead of ':' in its label parameter.
+goto +do_test_call
:test_call
echo Test CALL ok from %0
@@ -44,12 +46,69 @@ echo Test CALL ok from %0
goto :EOF
+::
+:: Next suite of tests.
+::
+
+:: GOTO label search algorithm ignores any whitespace between ':'
+:: and the label name, as well as leading and trailing whitespace.
+ :@tab@continue@space@@space@
+
+
+:: Jumping to a label with escape carets.
+goto :la^^bel2
+
+:la^bel2
+echo Unexpected GOTO jump^^!
+:la^^bel2
+echo GOTO with escape caret worked
+
+
+:: Go to the next tests below.
+goto :continue
+
+
::
:: Next suite of tests.
::
:continue
+::
+:: Extra GOTO syntax checks: separators in the label parameter
+::
+
+:: Whitespace
+goto :testLbl1@tab@ignored
+:testLbl1
+echo Hi there^^!
+
+:: Colon
+goto :testLbl2:ignored
+:testLbl2
+echo Hi there^^!
+
+:: Plus sign
+goto :testLbl3+ignored
+:testLbl3
+echo Hi there^^!
+
+:: Comma
+goto :testLbl4,ignored
+:testLbl4
+echo Hi there^^!
+
+:: Semicolon
+goto :testLbl5;ignored
+:testLbl5
+echo Hi there^^!
+
+:: Equals
+goto :testLbl6;ignored
+:testLbl6
+echo Hi there^^!
+
+
::
:: Testing :EOF support
::
diff --git a/modules/rostests/win32/cmd/test_goto_call.cmd.exp b/modules/rostests/win32/cmd/test_goto_call.cmd.exp
index f375908a87b..3e94300fd76 100644
--- a/modules/rostests/win32/cmd/test_goto_call.cmd.exp
+++ b/modules/rostests/win32/cmd/test_goto_call.cmd.exp
@@ -2,6 +2,13 @@
Test GOTO ok
--------- Testing CALL within batch ---------
Test CALL ok from :test_call
+GOTO with escape caret worked
+Hi there!
+Hi there!
+Hi there!
+Hi there!
+Hi there!
+Hi there!
--------- Testing :EOF support ---------
OK
OK
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=c5e6e5a19c38795162992…
commit c5e6e5a19c38795162992b3403542d3d67e4c38a
Author: Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
AuthorDate: Sun Jul 12 17:53:20 2020 +0200
Commit: Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
CommitDate: Wed Aug 19 20:36:01 2020 +0200
[CMD] GOTO: Fix handling of the ':EOF' label handling.
- The ':EOF' label feature is available only when extensions are enabled.
- Anything that follows the ':EOF' label, separated by at least one
whitespace character, is ignored, and the batch file terminates.
---
base/shell/cmd/goto.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/base/shell/cmd/goto.c b/base/shell/cmd/goto.c
index 585a9708072..5831b25e22f 100644
--- a/base/shell/cmd/goto.c
+++ b/base/shell/cmd/goto.c
@@ -65,8 +65,10 @@ INT cmd_goto(LPTSTR param)
++tmp;
*tmp = _T('\0');
- /* jump to end of the file */
- if ( _tcsicmp( param, _T(":eof"))==0)
+ /* Support jumping to the end of the file, only if extensions are enabled */
+ if (bEnableExtensions &&
+ (_tcsnicmp(param, _T(":EOF"), 4) == 0) &&
+ (!param[4] || _istspace(param[4])))
{
/* Position at the end of the batch file */
bc->mempos = bc->memsize;
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=f911bb482d2c7ba2ad2fc…
commit f911bb482d2c7ba2ad2fc57e734ee19bd245fcfb
Author: Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
AuthorDate: Fri May 22 23:58:12 2020 +0200
Commit: Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
CommitDate: Wed Aug 19 20:36:00 2020 +0200
[CMD] GOTO: The command should search labels from its position down to the end, then loop back to the beginning of the batch and down to the original position.
---
base/shell/cmd/goto.c | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/base/shell/cmd/goto.c b/base/shell/cmd/goto.c
index 10eeb28b12e..585a9708072 100644
--- a/base/shell/cmd/goto.c
+++ b/base/shell/cmd/goto.c
@@ -36,6 +36,8 @@
INT cmd_goto(LPTSTR param)
{
LPTSTR tmp, tmp2;
+ DWORD dwCurrPos;
+ BOOL bRetry;
TRACE("cmd_goto(\'%s\')\n", debugstr_aw(param));
@@ -74,14 +76,23 @@ INT cmd_goto(LPTSTR param)
return 0;
}
- /* jump to begin of the file */
- bc->mempos=0;
+ /*
+ * Search the next label starting our position, until the end of the file.
+ * If none has been found, restart at the beginning of the file, and continue
+ * until reaching back our old current position.
+ */
+ bRetry = FALSE;
+ dwCurrPos = bc->mempos;
+retry:
while (BatchGetString(textline, ARRAYSIZE(textline)))
{
INT pos;
INT_PTR size;
+ if (bRetry && (bc->mempos >= dwCurrPos))
+ break;
+
/* Strip out any trailing spaces or control chars */
tmp = textline + _tcslen(textline) - 1;
while (tmp > textline && (_istcntrl(*tmp) || _istspace(*tmp) || (*tmp == _T(':'))))
@@ -112,6 +123,12 @@ INT cmd_goto(LPTSTR param)
return 0;
}
}
+ if (!bRetry && (bc->mempos >= bc->memsize))
+ {
+ bRetry = TRUE;
+ bc->mempos = 0;
+ goto retry;
+ }
ConErrResPrintf(STRING_GOTO_ERROR2, param);
ExitBatch();
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=6eb1cae34819c275e0122…
commit 6eb1cae34819c275e01229d524a765e3340cdc5a
Author: Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
AuthorDate: Mon May 18 02:05:53 2020 +0200
Commit: Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
CommitDate: Wed Aug 19 20:35:58 2020 +0200
[CMD] Fixes for Batch error execution control flow.
CORE-13713 CORE-13736
- In case execution of all batch contexts is stopped (by selecting "All"
at the Ctrl-C/Ctrl-Break prompt), notify as well the CheckCtrlBreak()
signal handler once there are no more batch contexts (this in effect
resets the internal 'bLeaveAll' static flag in CheckCtrlBreak).
This is an adaptation of the fix present in FreeCOM 1.5, first
described in https://gcfl.net/FreeDOS/command.com/bugs074g.html .
- Introduce a ParseErrorEx() helper that sets the 'bParseError' flag and
displays a customized syntax-error message, only for the first syntax
error encountered. Implement ParseError() around the *Ex function.
- In batch mode, echo the original pre-parsed batch file line if a parse
error has been encountered.
- When running a compound command - including IF, FOR, command blocks -,
and that control flow is modified by any CALL/GOTO/EXIT command,
detect this while running the compound command so as to stop it and go
back to the main batch execution loop, that will then set up the actual
new command to run.
- In GOTO, do not process any more parts of a compound command only when
we have found a valid label.
---
base/shell/cmd/batch.c | 39 ++++++++++++++++++++++++++++++++++++---
base/shell/cmd/batch.h | 2 +-
base/shell/cmd/cmd.c | 22 ++++++++++++++++++----
base/shell/cmd/cmd.h | 12 ++++++++----
base/shell/cmd/for.c | 43 ++++++++++++++++++++++++++++++-------------
base/shell/cmd/goto.c | 10 +++++++++-
base/shell/cmd/if.c | 4 +---
base/shell/cmd/internal.c | 5 +++++
base/shell/cmd/parser.c | 15 +++++++++++----
9 files changed, 119 insertions(+), 33 deletions(-)
diff --git a/base/shell/cmd/batch.c b/base/shell/cmd/batch.c
index 6de2e657329..a5c31aece09 100644
--- a/base/shell/cmd/batch.c
+++ b/base/shell/cmd/batch.c
@@ -195,6 +195,24 @@ VOID ExitBatch(VOID)
cmd_endlocal(_T(""));
bc = bc->prev;
+
+#if 0
+ /* Do not process any more parts of a compound command */
+ bc->current = NULL;
+#endif
+
+ /* If there is no more batch contexts, notify the signal handler */
+ if (!bc)
+ CheckCtrlBreak(BREAK_OUTOFBATCH);
+}
+
+/*
+ * Exit all the nested batch calls.
+ */
+VOID ExitAllBatches(VOID)
+{
+ while (bc)
+ ExitBatch();
}
/*
@@ -343,7 +361,23 @@ INT Batch(LPTSTR fullname, LPTSTR firstword, LPTSTR param, PARSED_COMMAND *Cmd)
{
Cmd = ParseCommand(NULL);
if (!Cmd)
- continue;
+ {
+ if (!bParseError)
+ continue;
+
+ /* Echo the pre-parsed batch file line on error */
+ if (bEcho && !bDisableBatchEcho)
+ {
+ if (!bIgnoreEcho)
+ ConOutChar(_T('\n'));
+ PrintPrompt();
+ ConOutPuts(ParseLine);
+ ConOutChar(_T('\n'));
+ }
+ /* Stop all execution */
+ ExitAllBatches();
+ break;
+ }
/* JPP 19980807 */
/* Echo the command and execute it */
@@ -441,8 +475,7 @@ LPTSTR ReadBatchLine(VOID)
/* User halt */
if (CheckCtrlBreak(BREAK_BATCHFILE))
{
- while (bc)
- ExitBatch();
+ ExitAllBatches();
return NULL;
}
diff --git a/base/shell/cmd/batch.h b/base/shell/cmd/batch.h
index 23a5dc425d5..de425ee2f11 100644
--- a/base/shell/cmd/batch.h
+++ b/base/shell/cmd/batch.h
@@ -45,8 +45,8 @@ extern TCHAR textline[BATCH_BUFFSIZE]; /* Buffer for reading Batch file lines */
LPTSTR FindArg(TCHAR, BOOL *);
-LPTSTR BatchParams(LPTSTR, LPTSTR);
VOID ExitBatch(VOID);
+VOID ExitAllBatches(VOID);
INT Batch(LPTSTR, LPTSTR, LPTSTR, PARSED_COMMAND *);
BOOL BatchGetString(LPTSTR lpBuffer, INT nBufferLength);
LPTSTR ReadBatchLine(VOID);
diff --git a/base/shell/cmd/cmd.c b/base/shell/cmd/cmd.c
index 36de37ed79d..97f346af852 100644
--- a/base/shell/cmd/cmd.c
+++ b/base/shell/cmd/cmd.c
@@ -772,10 +772,22 @@ INT
ExecuteCommand(
IN PARSED_COMMAND *Cmd)
{
+#define SeenGoto() \
+ (bc && bc->current == NULL)
+
PARSED_COMMAND *Sub;
LPTSTR First, Rest;
INT Ret = 0;
+ /*
+ * Do not execute any command if we are about to exit CMD, or about to
+ * change batch execution context, e.g. in case of a CALL / GOTO / EXIT.
+ */
+ if (!Cmd)
+ return 0;
+ if (bExit || SeenGoto())
+ return 0;
+
if (!PerformRedirection(Cmd->Redirections))
return 1;
@@ -799,14 +811,14 @@ ExecuteCommand(
case C_QUIET:
case C_BLOCK:
case C_MULTI:
- for (Sub = Cmd->Subcommands; Sub; Sub = Sub->Next)
+ for (Sub = Cmd->Subcommands; Sub && !SeenGoto(); Sub = Sub->Next)
Ret = ExecuteCommand(Sub);
break;
case C_OR:
Sub = Cmd->Subcommands;
Ret = ExecuteCommand(Sub);
- if (Ret != 0)
+ if ((Ret != 0) && !SeenGoto())
{
nErrorLevel = Ret;
Ret = ExecuteCommand(Sub->Next);
@@ -816,7 +828,7 @@ ExecuteCommand(
case C_AND:
Sub = Cmd->Subcommands;
Ret = ExecuteCommand(Sub);
- if (Ret == 0)
+ if ((Ret == 0) && !SeenGoto())
Ret = ExecuteCommand(Sub->Next);
break;
@@ -835,6 +847,8 @@ ExecuteCommand(
UndoRedirection(Cmd->Redirections, NULL);
return Ret;
+
+#undef SeenGoto
}
INT
@@ -842,7 +856,7 @@ ExecuteCommandWithEcho(
IN PARSED_COMMAND *Cmd)
{
/* Echo the reconstructed command line */
- if (bEcho && !bDisableBatchEcho && Cmd->Type != C_QUIET)
+ if (bEcho && !bDisableBatchEcho && Cmd && (Cmd->Type != C_QUIET))
{
if (!bIgnoreEcho)
ConOutChar(_T('\n'));
diff --git a/base/shell/cmd/cmd.h b/base/shell/cmd/cmd.h
index 972a09ff446..a6e780bdc6e 100644
--- a/base/shell/cmd/cmd.h
+++ b/base/shell/cmd/cmd.h
@@ -28,10 +28,10 @@
#include "cmdver.h"
#include "cmddbg.h"
-#define BREAK_BATCHFILE 1
-#define BREAK_OUTOFBATCH 2
-#define BREAK_INPUT 3
-#define BREAK_IGNORE 4
+#define BREAK_BATCHFILE 1
+#define BREAK_OUTOFBATCH 2 /* aka. BREAK_ENDOFBATCHFILES */
+#define BREAK_INPUT 3
+#define BREAK_IGNORE 4
/* define some error messages */
#define D_ON _T("on")
@@ -338,6 +338,10 @@ VOID EchoCommand(PARSED_COMMAND *Cmd);
TCHAR *Unparse(PARSED_COMMAND *Cmd, TCHAR *Out, TCHAR *OutEnd);
VOID FreeCommand(PARSED_COMMAND *Cmd);
+void ParseErrorEx(LPTSTR s);
+extern BOOL bParseError;
+extern TCHAR ParseLine[CMDLINE_LENGTH];
+
/* Prototypes from PATH.C */
INT cmd_path (LPTSTR);
diff --git a/base/shell/cmd/for.c b/base/shell/cmd/for.c
index 08a5db3fa29..744d740b948 100644
--- a/base/shell/cmd/for.c
+++ b/base/shell/cmd/for.c
@@ -74,11 +74,12 @@ static BOOL GetNextElement(TCHAR **pStart, TCHAR **pEnd)
ExecuteCommandWithEcho((Cmd)->Subcommands)
/* Check if this FOR should be terminated early */
-static BOOL Exiting(PARSED_COMMAND *Cmd)
-{
- /* Someone might have removed our context */
- return bCtrlBreak || fc != Cmd->For.Context;
-}
+#define Exiting(Cmd) \
+ /* Someone might have removed our context */ \
+ (bCtrlBreak || (fc != (Cmd)->For.Context))
+/* Take also GOTO jumps into account */
+#define ExitingOrGoto(Cmd) \
+ (Exiting(Cmd) || (bc && bc->current == NULL))
/* Read the contents of a text file into memory,
* dynamically allocating enough space to hold it all */
@@ -251,7 +252,7 @@ static INT ForF(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer)
/* Loop over each file */
End = List;
- while (GetNextElement(&Start, &End))
+ while (!ExitingOrGoto(Cmd) && GetNextElement(&Start, &End))
{
FILE *InputFile;
LPTSTR FullInput, In, NextLine;
@@ -301,9 +302,9 @@ static INT ForF(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer)
}
/* Loop over the input line by line */
- In = FullInput;
- Skip = SkipLines;
- do
+ for (In = FullInput, Skip = SkipLines;
+ !ExitingOrGoto(Cmd) && (In != NULL);
+ In = NextLine)
{
DWORD RemainingTokens = Tokens;
LPTSTR *CurVar = Variables;
@@ -340,7 +341,8 @@ static INT ForF(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer)
/* Don't run unless the line had enough tokens to fill at least one variable */
if (*Variables[0])
Ret = RunInstance(Cmd);
- } while (!Exiting(Cmd) && (In = NextLine) != NULL);
+ }
+
cmd_free(FullInput);
}
@@ -360,6 +362,11 @@ static INT ForLoop(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer)
params[i] = _tcstol(Start, NULL, 0);
i = params[START];
+ /*
+ * Windows' CMD compatibility:
+ * Contrary to the other FOR-loops, FOR /L does not check
+ * whether a GOTO has been done, and will continue to loop.
+ */
while (!Exiting(Cmd) &&
(params[STEP] >= 0 ? (i <= params[END]) : (i >= params[END])))
{
@@ -379,7 +386,7 @@ static INT ForDir(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer, TCHAR *BufPos
INT Ret = 0;
TCHAR *Start, *End = List;
- while (!Exiting(Cmd) && GetNextElement(&Start, &End))
+ while (!ExitingOrGoto(Cmd) && GetNextElement(&Start, &End))
{
if (BufPos + (End - Start) > &Buffer[CMDLINE_LENGTH])
continue;
@@ -410,7 +417,7 @@ static INT ForDir(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer, TCHAR *BufPos
continue;
_tcscpy(FilePart, w32fd.cFileName);
Ret = RunInstance(Cmd);
- } while (!Exiting(Cmd) && FindNextFile(hFind, &w32fd));
+ } while (!ExitingOrGoto(Cmd) && FindNextFile(hFind, &w32fd));
FindClose(hFind);
}
else
@@ -436,6 +443,12 @@ static INT ForRecursive(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer, TCHAR *
Ret = ForDir(Cmd, List, Buffer, BufPos);
+ /* NOTE (We don't apply Windows' CMD compatibility here):
+ * Windows' CMD does not check whether a GOTO has been done,
+ * and will continue to loop. */
+ if (ExitingOrGoto(Cmd))
+ return Ret;
+
_tcscpy(BufPos, _T("*"));
hFind = FindFirstFile(Buffer, &w32fd);
if (hFind == INVALID_HANDLE_VALUE)
@@ -448,7 +461,11 @@ static INT ForRecursive(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer, TCHAR *
_tcscmp(w32fd.cFileName, _T("..")) == 0)
continue;
Ret = ForRecursive(Cmd, List, Buffer, _stpcpy(BufPos, w32fd.cFileName));
- } while (!Exiting(Cmd) && FindNextFile(hFind, &w32fd));
+
+ /* NOTE (We don't apply Windows' CMD compatibility here):
+ * Windows' CMD does not check whether a GOTO has been done,
+ * and will continue to loop. */
+ } while (!ExitingOrGoto(Cmd) && FindNextFile(hFind, &w32fd));
FindClose(hFind);
return Ret;
diff --git a/base/shell/cmd/goto.c b/base/shell/cmd/goto.c
index 2843e9618c1..10eeb28b12e 100644
--- a/base/shell/cmd/goto.c
+++ b/base/shell/cmd/goto.c
@@ -66,7 +66,11 @@ INT cmd_goto(LPTSTR param)
/* jump to end of the file */
if ( _tcsicmp( param, _T(":eof"))==0)
{
- bc->mempos=bc->memsize; /* position at the end of the batchfile */
+ /* Position at the end of the batch file */
+ bc->mempos = bc->memsize;
+
+ /* Do not process any more parts of a compound command */
+ bc->current = NULL;
return 0;
}
@@ -102,7 +106,11 @@ INT cmd_goto(LPTSTR param)
tmp2 = param;
/* Use whole label name */
if ((*tmp == _T(':')) && ((_tcsicmp(++tmp, param) == 0) || (_tcsicmp(tmp, ++tmp2) == 0)))
+ {
+ /* Do not process any more parts of a compound command */
+ bc->current = NULL;
return 0;
+ }
}
ConErrResPrintf(STRING_GOTO_ERROR2, param);
diff --git a/base/shell/cmd/if.c b/base/shell/cmd/if.c
index 723bce32972..26f56dee95a 100644
--- a/base/shell/cmd/if.c
+++ b/base/shell/cmd/if.c
@@ -187,9 +187,7 @@ INT ExecuteIf(PARSED_COMMAND *Cmd)
else
{
/* Full condition was false, do the "else" command if there is one */
- if (Cmd->Subcommands->Next)
- return ExecuteCommand(Cmd->Subcommands->Next);
- return 0;
+ return ExecuteCommand(Cmd->Subcommands->Next);
}
}
diff --git a/base/shell/cmd/internal.c b/base/shell/cmd/internal.c
index d758c151a4c..9febc003cdc 100644
--- a/base/shell/cmd/internal.c
+++ b/base/shell/cmd/internal.c
@@ -531,9 +531,14 @@ INT CommandExit(LPTSTR param)
* otherwise exit this command interpreter instance.
*/
if (bc)
+ {
+ bc->current = NULL;
ExitBatch();
+ }
else
+ {
bExit = TRUE;
+ }
}
else
{
diff --git a/base/shell/cmd/parser.c b/base/shell/cmd/parser.c
index 88787efc7f5..e25b63aa91b 100644
--- a/base/shell/cmd/parser.c
+++ b/base/shell/cmd/parser.c
@@ -60,9 +60,9 @@ enum
/* Scratch buffer for temporary command substitutions / expansions */
static TCHAR TempBuf[CMDLINE_LENGTH];
-static BOOL bParseError;
+/*static*/ BOOL bParseError;
static BOOL bLineContinuations;
-static TCHAR ParseLine[CMDLINE_LENGTH];
+/*static*/ TCHAR ParseLine[CMDLINE_LENGTH];
static TCHAR *ParsePos;
static TCHAR CurChar;
@@ -108,12 +108,19 @@ restart:
return (CurChar = Char);
}
-static void ParseError(void)
+void ParseErrorEx(LPTSTR s)
{
- error_syntax(CurrentTokenType != TOK_END ? CurrentToken : NULL);
+ /* Only display the first error we encounter */
+ if (!bParseError)
+ error_syntax(s);
bParseError = TRUE;
}
+static void ParseError(void)
+{
+ ParseErrorEx(CurrentTokenType != TOK_END ? CurrentToken : NULL);
+}
+
/*
* Yes, cmd has a Lexical Analyzer. Whenever the parser gives an "xxx was
* unexpected at this time." message, it shows what the last token read was.
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=7f8792e0056f6763cf2b3…
commit 7f8792e0056f6763cf2b3de0ca052a35f784fa8b
Author: Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
AuthorDate: Mon Jul 27 00:30:24 2020 +0200
Commit: Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
CommitDate: Wed Aug 19 20:35:58 2020 +0200
[CMD_ROSTEST] Add tests for batch execution flow with GOTO and CALL commands.
- Tests for GOTO across IF parenthesized block (parser test with line
continuation at closing parenthesis).
- Tests for showing how FOR-loops stop when a GOTO is encountered.
- Tests for EXIT command within IF block.
---
modules/rostests/win32/cmd/rsrc.rc | 6 +
modules/rostests/win32/cmd/test_goto_call.cmd | 239 ++++++++++++++++++++++
modules/rostests/win32/cmd/test_goto_call.cmd.exp | 214 +++++++++++++++++++
3 files changed, 459 insertions(+)
diff --git a/modules/rostests/win32/cmd/rsrc.rc b/modules/rostests/win32/cmd/rsrc.rc
index 7196b345f36..638cd3ae1a5 100644
--- a/modules/rostests/win32/cmd/rsrc.rc
+++ b/modules/rostests/win32/cmd/rsrc.rc
@@ -28,3 +28,9 @@ test_echoer_parser.cmd TESTCMD "test_echoer_parser.cmd"
/* @makedep: test_echoer_parser.cmd.exp */
test_echoer_parser.cmd.exp TESTOUT "test_echoer_parser.cmd.exp"
+
+/* @makedep: test_goto_call.cmd */
+test_goto_call.cmd TESTCMD "test_goto_call.cmd"
+
+/* @makedep: test_goto_call.cmd.exp */
+test_goto_call.cmd.exp TESTOUT "test_goto_call.cmd.exp"
diff --git a/modules/rostests/win32/cmd/test_goto_call.cmd b/modules/rostests/win32/cmd/test_goto_call.cmd
new file mode 100644
index 00000000000..44add4cd9ec
--- /dev/null
+++ b/modules/rostests/win32/cmd/test_goto_call.cmd
@@ -0,0 +1,239 @@
+@echo off
+setlocal enableextensions
+setlocal enabledelayedexpansion
+
+
+::
+:: Tests for GOTO and CALL.
+::
+
+
+
+::
+:: Testing GOTO/CALL from and to within parenthesized blocks.
+::
+
+echo --------- Testing GOTO within block ---------
+(echo Block-test 1: Single-line& goto :block2 & echo Unexpected Block-test 1^^!)
+echo Unexpected echo 1^^!
+
+:block2
+(
+echo Block-test 2: Multi-line
+goto :block3
+echo Unexpected Block-test 2^^!
+)
+echo Unexpected echo 2-3^^!
+
+:test_call_block
+echo Test CALL in block OK from %0
+:: We exit this CALL invocation
+goto :EOF
+
+(
+:block3
+echo --------- Testing CALL within block ---------
+echo Block-test 3: CALL in block
+call :test_call_block
+echo CALL done
+)
+
+goto :block4
+echo Unexpected echo 4^^!
+(
+:block4
+echo Block-test 4 OK
+)
+
+
+::
+:: Testing GOTO/CALL from within FOR and IF.
+:: This is a situation similar to the parenthesized blocks.
+:: See bug-report CORE-13713
+::
+
+:: Testing CALL within FOR
+echo --------- Testing CALL within FOR ---------
+for /L %%A IN (0,1,3) DO (
+ set Number=%%A
+ if %%A==2 call :out_of_loop_1 %%A
+ if %%A==2 (echo %%A IS equal to 2) else (echo %%A IS NOT equal to 2)
+)
+goto :continue_2
+:out_of_loop_1
+echo Out of FOR 1 CALL from %0, number is %1
+:: We exit this CALL invocation
+goto :EOF
+:continue_2
+
+
+:: Testing GOTO within FOR
+echo --------- Testing GOTO within FOR ---------
+for /L %%A IN (0,1,3) DO (
+ set Number=%%A
+ if %%A==2 goto :out_of_loop_2
+ echo %%A IS NOT equal to 2
+)
+echo Unexpected FOR echo 2^^!
+:out_of_loop_2
+echo Out of FOR 2, number is %Number%
+
+
+
+::
+:: Show how each different FOR-loop stops when a GOTO is encountered.
+::
+echo --------- Testing FOR loop stopping with GOTO ---------
+
+:: FOR - Stops directly
+echo --- FOR
+@echo on
+for %%A in (1,2,3,4,5,6,7,8,9,10) do (
+ set Number=%%A
+ if %%A==5 goto :out_of_loop_2a
+)
+echo Unexpected FOR echo 2a^^!
+:out_of_loop_2a
+echo Out of FOR 2a, number is %Number%
+@echo off
+
+
+:: FOR /R - Stops directly
+echo --- FOR /R
+
+:: Use auxiliary directoreis to test for /R
+mkdir foobar && cd foobar
+mkdir foo1
+mkdir foo2
+mkdir bar1
+
+@echo on
+for /r %%A in (1,2,3,4,5,6,7,8,9,10) do (
+ set Number=%%~nA
+ if %%~nA==5 goto :out_of_loop_2b
+)
+echo Unexpected FOR echo 2b^^!
+:out_of_loop_2b
+echo Out of FOR 2b, number is %Number%
+@echo off
+
+:: Cleanup
+cd .. & rd /s/q foobar
+
+
+:: FOR /L - Does not stop directly. It continues looping until the end
+:: but does not execute its body code. This can cause problems e.g. for
+:: infinite loops "for /l %a in () do ( ... )" that are exited by EXIT /B,
+:: since the body code stops being executed, but the loop itself continues
+:: running forever.
+echo --- FOR /L
+@echo on
+for /l %%A in (1,1,10) do (
+ set Number=%%A
+ if %%A==5 goto :out_of_loop_2c
+)
+echo Unexpected FOR echo 2c^^!
+:out_of_loop_2c
+echo Out of FOR 2c, number is %Number%
+@echo off
+
+
+:: FOR /F - Stops directly.
+echo --- FOR /F
+@echo on
+for %%T in ( "1:2:3" "4:5:6:7" "8:9:10" ) do (
+ set "pc=%%~T"
+ for /f "delims=" %%A in (^"!pc::^=^
+% New line %
+!^") do (
+
+ set Number=%%A
+ if %%A==5 goto :out_of_loop_2d
+)
+)
+echo Unexpected FOR echo 2d^^!
+:out_of_loop_2d
+echo Out of FOR 2d, number is %Number%
+@echo off
+
+
+
+:: Testing CALL within IF
+echo --------- Testing CALL within IF ---------
+if 1==1 (
+ call :out_of_if_1 123
+ echo Success IF echo 1
+)
+goto :continue_3
+:out_of_if_1
+echo Out of IF CALL from %0, number is %1
+:: We exit this CALL invocation
+goto :EOF
+:continue_3
+
+
+:: Testing GOTO within IF
+echo --------- Testing GOTO within IF ---------
+if 1==1 (
+ goto :out_of_if_2
+ echo Unexpected IF echo 2a^^!
+)
+echo Unexpected IF echo 2b^^!
+:out_of_if_2
+echo Out of IF ok
+
+:: Same, but with line-continuation at the closing parenthesis of the IF block.
+if 1==1 (
+:labelA
+ echo A
+) ^
+else (
+:labelB
+ echo B
+ goto :continue
+)
+:: We are jumping inside the IF, whose block will be interpreted as
+:: separate commands; thus we will also run the :labelB block as well.
+goto :labelA
+
+
+::
+:: Next suite of tests.
+::
+:continue
+
+:: Testing EXIT within IF
+echo --------- Testing EXIT within IF ---------
+
+:: Use a CALL context, and we will only check EXIT /B.
+call :doExitIfTest 1
+call :doExitIfTest 2
+goto :finished
+
+:doExitIfTest
+if %1==1 (
+ echo First block
+ exit /b
+ echo Unexpected first block^^!
+) else (
+ echo Second block
+ exit /b
+ echo Unexpected second block^^!
+)
+echo You won't see this^^!
+exit /b
+
+
+
+::
+:: Finished!
+::
+:finished
+echo --------- Finished --------------
+goto :EOF
+
+:: Subroutine to set errorlevel and return
+:: in windows nt 4.0, this always sets errorlevel 1, since /b isn't supported
+:setError
+exit /B %1
+:: This line runs under cmd in windows NT 4, but not in more modern versions.
diff --git a/modules/rostests/win32/cmd/test_goto_call.cmd.exp b/modules/rostests/win32/cmd/test_goto_call.cmd.exp
new file mode 100644
index 00000000000..802e6c5dca3
--- /dev/null
+++ b/modules/rostests/win32/cmd/test_goto_call.cmd.exp
@@ -0,0 +1,214 @@
+--------- Testing GOTO within block ---------
+Block-test 1: Single-line
+Block-test 2: Multi-line
+--------- Testing CALL within block ---------
+Block-test 3: CALL in block
+Test CALL in block OK from :test_call_block
+CALL done
+Block-test 4 OK
+--------- Testing CALL within FOR ---------
+0 IS NOT equal to 2
+1 IS NOT equal to 2
+Out of FOR 1 CALL from :out_of_loop_1, number is 2
+2 IS equal to 2
+3 IS NOT equal to 2
+--------- Testing GOTO within FOR ---------
+0 IS NOT equal to 2
+1 IS NOT equal to 2
+Out of FOR 2, number is 2
+--------- Testing FOR loop stopping with GOTO ---------
+--- FOR
+
+@pwd@>for %A in (1 2 3 4 5 6 7 8 9 10) do (
+set Number=%A@space@@space@
+ if %A == 5 goto :out_of_loop_2a@space@
+)@space@
+
+@pwd@>(
+set Number=1@space@@space@
+ if 1 == 5 goto :out_of_loop_2a@space@
+)@space@
+
+@pwd@>(
+set Number=2@space@@space@
+ if 2 == 5 goto :out_of_loop_2a@space@
+)@space@
+
+@pwd@>(
+set Number=3@space@@space@
+ if 3 == 5 goto :out_of_loop_2a@space@
+)@space@
+
+@pwd@>(
+set Number=4@space@@space@
+ if 4 == 5 goto :out_of_loop_2a@space@
+)@space@
+
+@pwd@>(
+set Number=5@space@@space@
+ if 5 == 5 goto :out_of_loop_2a@space@
+)@space@
+
+@pwd@>echo Out of FOR 2a, number is 5@space@
+Out of FOR 2a, number is 5
+--- FOR /R
+
+@pwd@\foobar>for /R %A in (1 2 3 4 5 6 7 8 9 10) do (
+set Number=%~nA@space@@space@
+ if %~nA == 5 goto :out_of_loop_2b@space@
+)@space@
+
+@pwd@\foobar>(
+set Number=1@space@@space@
+ if 1 == 5 goto :out_of_loop_2b@space@
+)@space@
+
+@pwd@\foobar>(
+set Number=2@space@@space@
+ if 2 == 5 goto :out_of_loop_2b@space@
+)@space@
+
+@pwd@\foobar>(
+set Number=3@space@@space@
+ if 3 == 5 goto :out_of_loop_2b@space@
+)@space@
+
+@pwd@\foobar>(
+set Number=4@space@@space@
+ if 4 == 5 goto :out_of_loop_2b@space@
+)@space@
+
+@pwd@\foobar>(
+set Number=5@space@@space@
+ if 5 == 5 goto :out_of_loop_2b@space@
+)@space@
+
+@pwd@\foobar>echo Out of FOR 2b, number is 5@space@
+Out of FOR 2b, number is 5
+--- FOR /L
+
+@pwd@>for /L %A in (1 1 10) do (
+set Number=%A@space@@space@
+ if %A == 5 goto :out_of_loop_2c@space@
+)@space@
+
+@pwd@>(
+set Number=1@space@@space@
+ if 1 == 5 goto :out_of_loop_2c@space@
+)@space@
+
+@pwd@>(
+set Number=2@space@@space@
+ if 2 == 5 goto :out_of_loop_2c@space@
+)@space@
+
+@pwd@>(
+set Number=3@space@@space@
+ if 3 == 5 goto :out_of_loop_2c@space@
+)@space@
+
+@pwd@>(
+set Number=4@space@@space@
+ if 4 == 5 goto :out_of_loop_2c@space@
+)@space@
+
+@pwd@>(
+set Number=5@space@@space@
+ if 5 == 5 goto :out_of_loop_2c@space@
+)@space@
+
+@pwd@>(
+set Number=6@space@@space@
+ if 6 == 5 goto :out_of_loop_2c@space@
+)@space@
+
+@pwd@>(
+set Number=7@space@@space@
+ if 7 == 5 goto :out_of_loop_2c@space@
+)@space@
+
+@pwd@>(
+set Number=8@space@@space@
+ if 8 == 5 goto :out_of_loop_2c@space@
+)@space@
+
+@pwd@>(
+set Number=9@space@@space@
+ if 9 == 5 goto :out_of_loop_2c@space@
+)@space@
+
+@pwd@>(
+set Number=10@space@@space@
+ if 10 == 5 goto :out_of_loop_2c@space@
+)@space@
+
+@pwd@>echo Out of FOR 2c, number is 5@space@
+Out of FOR 2c, number is 5
+--- FOR /F
+
+@pwd@>for %T in ("1:2:3" "4:5:6:7" "8:9:10") do (
+set "pc=%~T"@space@@space@
+ for /F "delims=" %A in ("!pc::=
+!") do (
+set Number=%A@space@@space@
+ if %A == 5 goto :out_of_loop_2d@space@
+)@space@
+)@space@
+
+@pwd@>(
+set "pc=1:2:3"@space@@space@
+ for /F "delims=" %A in ("!pc::=
+!") do (
+set Number=%A@space@@space@
+ if %A == 5 goto :out_of_loop_2d@space@
+)@space@
+)@space@
+
+@pwd@>(
+set Number=1@space@@space@
+ if 1 == 5 goto :out_of_loop_2d@space@
+)@space@
+
+@pwd@>(
+set Number=2@space@@space@
+ if 2 == 5 goto :out_of_loop_2d@space@
+)@space@
+
+@pwd@>(
+set Number=3@space@@space@
+ if 3 == 5 goto :out_of_loop_2d@space@
+)@space@
+
+@pwd@>(
+set "pc=4:5:6:7"@space@@space@
+ for /F "delims=" %A in ("!pc::=
+!") do (
+set Number=%A@space@@space@
+ if %A == 5 goto :out_of_loop_2d@space@
+)@space@
+)@space@
+
+@pwd@>(
+set Number=4@space@@space@
+ if 4 == 5 goto :out_of_loop_2d@space@
+)@space@
+
+@pwd@>(
+set Number=5@space@@space@
+ if 5 == 5 goto :out_of_loop_2d@space@
+)@space@
+
+@pwd@>echo Out of FOR 2d, number is 5@space@
+Out of FOR 2d, number is 5
+--------- Testing CALL within IF ---------
+Out of IF CALL from :out_of_if_1, number is 123
+Success IF echo 1
+--------- Testing GOTO within IF ---------
+Out of IF ok
+A
+A
+B
+--------- Testing EXIT within IF ---------
+First block
+Second block
+--------- Finished --------------