How batch macros work
Posted: 12 Jun 2011 20:00
☺
It is currently 05 Sep 2011 08:26
The project was moved to new location
http://www.mediafire.com/myfiles.php#3p17oxw36b33s,1
This page is outdated !
Creating a macro is suprisingly easy, a macro is just another name for a variable with some special characteristics.
We simply create a variable by giving it a value:
the important thing are the quotes, as this won't work:
it is interpreted as:
not recognized as a valid command.
Then there are escapes ^, as the name suggests they are used to escape charcters:
it is interpreted as:
a valid command.
Wait a minute ! We created our first macro
With the escape character ^ we can do line continuations:
it is interpreted as:
It's called a macro because unlike a variable that has just some text in it, the text is a valid command, for as far as dos is concerned.
It will just expand a variable into memory when it's used:
it is interpreted as:
Let's direct expand it:
it is interpreted as:
we define a linefeed variable like this, we must first define it in order to use it:
But we need to recreate the linefeed at least once to get a newline:
Now we can use it :
It is the same as & in a way, because we can get the same results with it.
these commands do exactly the same. they set variable to value & echo value.
this character is the delayed expansion character !, you could also use %, the direct expansion character.
but they both invoke their own set of limitations & possibilities.
I normally default to setlocal EnableDelayedExpansion, this enables me to evaluate inside for:
it is interpreted as:
We can simply use a macro within a macro:
But to use the newline in a macro, we have to recreate it another time:
Our macro needs input if we want to do something usefull with it. We simply use for:
And while were at it, why not create a macro out of this :
It works
Let's define something usefull now:
Now we just expand if needed:
The sky is the limit, or is it your imagination :geek:
The amount of characters a variable can contain, is limited !
( The "normal" maximum line length is ~8192, the maximum line length for drag&drop operations is ~2048. And even the 8192 can be exceeded with an FOR /F loop. jeb )
Assigning any more will result in a syntax error at defenition phase !
This is the only problem I cannot overcome.
And it's a very nasty problem, because it prohibits me from writing complex macro's.
But we can try to work around this problem:
Complex macro's often refer internally to other macro's. Remember we can do that !
But they are expanded at definition phase. That's why a macro that is overloaded fails at this very moment.
So what makes our macro big ? Lets zoom into something expert:
But wait !, an error event shouldn't occur ! That's like aborting the hole batch script ! But why is this spacetaking code inside my macro ? Workaround:
And this is the workaround, Remember our functions ?:
We are doing a function call, exactly what we wanted to avoid in the first place, our macro just lost a couple of hundreds of millisecs !
But now we pushed the limits a little higher and are allowing our macro to grow in complexity.
Besides fatal errors should never occur, that's why we don't loose any speed !
As if ^^^!$error^^^! gtr 0 should always be false !
It is currently 05 Sep 2011 08:26
The project was moved to new location
http://www.mediafire.com/myfiles.php#3p17oxw36b33s,1
This page is outdated !
Creating a macro is suprisingly easy, a macro is just another name for a variable with some special characteristics.
We simply create a variable by giving it a value:
Code: Select all
set "$variable=value"
the important thing are the quotes, as this won't work:
Code: Select all
set variable=value&value
it is interpreted as:
Code: Select all
set variable=value
value
not recognized as a valid command.
Then there are escapes ^, as the name suggests they are used to escape charcters:
Code: Select all
set variable=value^&echo.value
it is interpreted as:
Code: Select all
set variable=value &echo.value
a valid command.
Wait a minute ! We created our first macro
With the escape character ^ we can do line continuations:
Code: Select all
set var=^
hello
it is interpreted as:
Code: Select all
set var=hello
It's called a macro because unlike a variable that has just some text in it, the text is a valid command, for as far as dos is concerned.
It will just expand a variable into memory when it's used:
Code: Select all
set "@\necho=echo. &echo."
it is interpreted as:
Code: Select all
echo.
echo.
Let's direct expand it:
Code: Select all
%@\necho% Hello There
it is interpreted as:
Code: Select all
echo.
echo. Hello There
we define a linefeed variable like this, we must first define it in order to use it:
Code: Select all
set \LF=^
:: Two empty lines are neccessary
But we need to recreate the linefeed at least once to get a newline:
Code: Select all
set ^"\n=^%\LF%%\LF%"
Now we can use it :
Code: Select all
echo.hello!\n!There
Code: Select all
echo.hello
echo.There
It is the same as & in a way, because we can get the same results with it.
Code: Select all
"set variable=value &echo.value"
"set variable=value !\n!echo.value"
these commands do exactly the same. they set variable to value & echo value.
this character is the delayed expansion character !, you could also use %, the direct expansion character.
but they both invoke their own set of limitations & possibilities.
I normally default to setlocal EnableDelayedExpansion, this enables me to evaluate inside for:
Code: Select all
for %%! in ( something else ) set "var=%%!" &echo. !var!
it is interpreted as:
Code: Select all
set "var=something" &echo.something
set "var=else" &echo.else
We can simply use a macro within a macro:
Code: Select all
set "@test=set /a $testcount += 1 &%@\necho% test : '^^^!$testcount^^^!' &%@Pause%"
But to use the newline in a macro, we have to recreate it another time:
Code: Select all
set ^"\n2=^^^%\LF%%\LF%^%\LF%%\LF%^^^"
Our macro needs input if we want to do something usefull with it. We simply use for:
Code: Select all
for /l %%! in
And while were at it, why not create a macro out of this :
Code: Select all
set "@forC=for /l %%^^^! in"
It works
Code: Select all
set /a Available_errorlevels = 99
::
echo. @Available_errorlevels = '%Available_errorlevels%'
::
%@forC% (
2, 1, %Available_errorlevels%
) do for %%! in (
"$%%~!.error"
) do echo.set ^"%%~!=$%%~!.error unset ^^^!"
::
set "Available_errorlevels="
Let's define something usefull now:
Code: Select all
set ^"@GetRandom=do ( %\n2%
call set /a $StdOut = %%Random%% %%%% ^^^^^^^( $Maximum - $Minimum + 1 ^^^^^^^) + $Minimum %\n2%
)"
Now we just expand if needed:
Code: Select all
set "@forA=for /f "usebackq tokens=1-26 delims=¦" %%a in"
%@forA% ( '"$StoreVAR"¦"$Mininmum"¦"$Maximum"' ) %@GetRandom%
The sky is the limit, or is it your imagination :geek:
Code: Select all
::--------------------------------------------------------------------------------------------------------------------------
%@Pre% @GetRandom
::
:: %@forA% ( '"$Store"¦"0"¦"1000"' ) %@GetRandom%
::
::(
set "$Defines=@GetRandom"
::
set ^"$Usage.%$Defines%=^
% % Usage : %%@forA%% !\n!^
% % ^( '"$StoreVAR"¦"$Mininmum"¦"$Maximum"' ^) !\n!^
% % %%%$Defines%%% !\n!^
% % input: !\n!^
% % $StoreVAR: required : String : name of return variable !\n!^
% % $Mininmum: required : StrVar : Lower boundary !\n!^
% % $Maximum : required : StrVar : Upper boundary !\n!^
% % ToConsole: !\n!^
% % Contents of $StoreVAR !\n!^
% % The output can be redirected. !\n!^
% % Return: !\n!^
% % $error: 0 for succes, panic otherwise "
::
set ^"%$Defines%=do ( %\n2%
!@forTS! ( "%$Defines%"¦"req:str:$StoreVAR=%%~a"¦"req:SVr:$Mininmum=%%~b"¦"req:SVr:$Maximum=%%~c" ) !@LeadIn! %\n2%
rem !@forTS! ( "@DebugIn" ) !@CallMacro! %\n2%
Setlocal EnableDelayedExpansion %\n2%
call set /a $StdOut = %%Random%% %%%% ^^^^^^^( $Maximum - $Minimum + 1 ^^^^^^^) + $Minimum %\n2%
!@forTS! ( %\n2%
"^!$StoreVAR^!=^!$StdOut^!" %\n2%
) !@PassVarOverEndlocal! %\n2%
) ^&( %\n2%
!@\necho! %\n2%
call set /p "?= %%~a : ^!%%~a^! [^!$0.error^!]" ^<nul %\n2%
rem !@forTS! ( "@DebugOut" ) !@CallMacro! %\n2%
!@LeadOut! %\n2%
)"
::)
%@Post% @GetRandom [OK]
::--------------------------------------------------------------------------------------------------------------------------
The amount of characters a variable can contain, is limited !
( The "normal" maximum line length is ~8192, the maximum line length for drag&drop operations is ~2048. And even the 8192 can be exceeded with an FOR /F loop. jeb )
Assigning any more will result in a syntax error at defenition phase !
This is the only problem I cannot overcome.
And it's a very nasty problem, because it prohibits me from writing complex macro's.
But we can try to work around this problem:
Complex macro's often refer internally to other macro's. Remember we can do that !
Code: Select all
::--------------------------------------------------------------------------------------------------------------------------
%@\necho% ^>^> Pre Define @Pre
::(
set "@Pre=%@\necho% ^>^> Pre Define"
::)
echo. ^<^< Post Define @Pre [OK]
::--------------------------------------------------------------------------------------------------------------------------
But they are expanded at definition phase. That's why a macro that is overloaded fails at this very moment.
So what makes our macro big ? Lets zoom into something expert:
Code: Select all
expand %@getmacro% >> if error = then else if then else for then...
But wait !, an error event shouldn't occur ! That's like aborting the hole batch script ! But why is this spacetaking code inside my macro ? Workaround:
Code: Select all
::--------------------------------------------------------------------------------------------------------------------------
%@Pre% @IfErrorBoolExit
::(
set ^"@IfErrorBoolExit=( %\n2%
if /i ["^!$error.bool^!"] neq ["0"] !@IfErrorExit! %\n2%
)"
::)
%@Post% @IfErrorBoolExit [OK]
::--------------------------------------------------------------------------------------------------------------------------
And this is the workaround, Remember our functions ?:
Code: Select all
::--------------------------------------------------------------------------------------------------------------------------
%@Pre% @IfErrorExit
::(
set ^"@IfErrorExit=( %\n2%
if ^^^!$error^^^! gtr 0 !@forTS! ( "@PANIC" ) !@CallMacro! %\n2%
)"
::)
%@Post% @IfErrorExit [OK]
::--------------------------------------------------------------------------------------------------------------------------
We are doing a function call, exactly what we wanted to avoid in the first place, our macro just lost a couple of hundreds of millisecs !
Code: Select all
!@forTS! ( "@PANIC" ) !@CallMacro! %\n2%
for %%! in ( "@PANIC" ) do ( call :§CallMacro %%~! )
But now we pushed the limits a little higher and are allowing our macro to grow in complexity.
Besides fatal errors should never occur, that's why we don't loose any speed !
As if ^^^!$error^^^! gtr 0 should always be false !
Code: Select all
::--------------------------------------------------------------------------------------------------------------------------
%@Pre% @PANIC
::(
set ^"$PANIC=^
% % !\n!^
% % 00000 0 00 0 0000000 0000 !\n!^
% % 0 0 0 0 00 0 0 0 !\n!^
% % 0 0 0 0 0 0 0 0 0 !\n!^
% % 0 0 0 0 0 0 0 0 0 !\n!^
% % 0 0 0 0 0 0 0 0 0 !\n!^
% % 00000 00000 0 0 0 0 0 !\n!^
% % 0 0 0 0 0 0 0 0 !\n!^
% % 0 0 0 0 0 0 0 0 !\n!^
% % 0 0 0 0 0 0 0 0 !\n!^
% % 0 0 0 0 00 0 0 !\n!^
% % 0 0 0 0 00 0000000 0000 "
::
set ^"@PANIC=( %\n2%
!@Get_Error_from_ErrorLevel_NotZero! %\n2%
!@\necho! ^&set /p "?= Function: " ^<nul %\n2%
SetLocal EnableExtensions EnableDelayedExpansion %\n2%
set /a $error = 0 %\n2%
if not defined $Debug.Array ( %$6.error=attempt to use $Debug.Array while undefined% %\n2%
if defined $Usage.@DeclareArray ( %\n2%
set /p "?=@PANIC on $Debug.Array" ^<nul %\n2%
!@forTS! ^( "$Usage.@DeclareArray" ^) !@SeeVarsNoErr! %\n2%
echo. ^&set /p "?= Fatality: " ^<nul %\n2%
set /a $error = 6 %\n2%
) else ( %$7.error=attempt to use an array undefined% %\n2%
set /p "?=@PANIC on $Usage.@DeclareArray" ^<nul %\n2%
echo. ^&set /p "?= Fatality: " ^<nul %\n2%
set /a $error = 7 %\n2%
) %\n2%
) %\n2%
if ^^^!$error^^^! equ 0 ( %\n2%
set /a $count = 0 %\n2%
set "$Debug.Array=^!$Debug.Array:¦= ^!" %\n2%
for %%^^^? in ( %\n2%
^^^!$Debug.Array^^^! %\n2%
) do ( %\n2%
set /a $count += 1 %\n2%
set "$Debug.^!$count^!=%%~^?" %\n2%
) %\n2%
set "$Debug.Array=^!$Debug.Array: =¦^!" %\n2%
set /a $Debug.0 = ^^^!$count^^^! %\n2%
if not defined $Usage.^^^!$Debug.1^^^! ( %$7.error=attempt to use an array undefined% %\n2%
set /p "?=@PANIC on $Usage.^!$Debug.1^!" ^<nul %\n2%
echo. ^&set /p "?= Fatality: " ^<nul %\n2%
set /a $error = 7 %\n2%
) else ( %\n2%
set /p "?='^!$Debug.1^!'" ^<nul %\n2%
echo. ^&!@forTS! ( "$Usage.^!$Debug.1^!" ) !@SeeVarsNoErr! %\n2%
!@\necho! ^&set /p "?= Fatality: " ^<nul %\n2%
echo. ^&set /p "?= Traces: '^!$Debug.Array^!'" ^<nul %\n2%
echo. ^&set /p "?= Caller: '^!$Debug.2^!'" ^<nul %\n2%
echo. ^&set /p "?= Called: '^!$Debug.1^!'" ^<nul %\n2%
echo. ^&set /p "?= LInput: '^!$LInput^!'" ^<nul %\n2%
) %\n2%
) %\n2%
if ^^^!$error^^^! neq 0 !@ExitFATAL! %\n2%
EndLocal %\n2%
!@ExitFATAL! %\n2%
)"
::)
%@Post% @PANIC [OK]
::--------------------------------------------------------------------------------------------------------------------------
Code: Select all
::--------------------------------------------------------------------------------------------------------------------------
:§ExitFATAL "()"
::(
"!FullPathFile.CSOUND!" 220^(2^) 294^(4^)
::
for /l %%! in (
1, 1, 10
) do (
color 0C &>nul "!FullPathFile.CHOICE!" /c:ny /t:n,2 /n &if Errorlevel 2 exit /b
color C0 &>nul "!FullPathFile.CHOICE!" /c:ny /t:n,1 /n &if Errorlevel 2 exit /b
)
::
goto :§ExitFATAL "()"
::
exit /b
::)
::--------------------------------------------------------------------------------------------------------------------------