SET/A: Macro to expand function results in SET /A expression
Posted: 26 Dec 2011 23:51
Batch macros with parameters make possible to redefine the syntax of Batch language and give access to complex procedures in a simple way. Let's start with a smaller example before we immerse in SET/A macro description.
Previous "SET-Substring" macro replace part of the current "variable" value with another substring; the part to replace is given in the same way as the standard Batch substring extraction with the sole difference that it does not end at the last character by default, so this point must be explicitly indicated by -0 in "size". If a substring is inserted beyond the variable end, the original value is padded with spaces up to that place. To append a substring at variable end, put -0 in "start".Results:
SET/A is a macro that execute a SET /A command, but first replace the values returned by Batch functions. For example:The functions are standard Batch subroutines with a last parameter that indicate the variable that recieve the numeric result. For example:If the Batch function takes its numeric parameters via SET /A commands...... then function parameters in %SET/A% macro may also include expressions:
Remember that Batch parameter delimiters may be comma, semicolon or equal-sign, besides spaces, so "FUNC3(P1,P2,P3)" is the same as "FUNC3(P1 P2 P3)" and also "FUNC3(P1;P2=P3)". If a parameter may include anyone of these characters, it must be enclosed in quotes.
This is SET/A macro:Results:
The original SET /A command may assign values to several variables in the same expression:
To do the same thing in a local environment, SET/A macro would need to complete a much more detailed analysis of the expression and end with an ENDLOCAL command followed by several variable assignments; this behavior is beyond its original design. If SETLOCAL/ENDLOCAL commands are omitted in SET/A macro, the variable assignments in its expression will be directly done in the same environment of the caller program.
To avoid modification of local variables, all variable names used in SET/A macro have the "setA." prefix. However, if %set/a% macro would be used in a nested way, the function that create the nesting MUST include a SETLOCAL/ENDLOCAL pair, as described below.
EDIT: I slightly modified StrLen function so it can get the string both as variable name or "constant" in quotes.
Antonio
Code: Select all
%SET/S% variable:~start,size=substring to insert
Code: Select all
@echo off
::SET/S - Macro that replace a substring in a variable
::Antonio Perez Ayala - Dec/26/2011
setlocal DisableDelayedExpansion
set LF=^
::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
:SET/S variable:~start[,size]=substring
set set/s=for %%n in (1 2) do if %%n==2 (%\n%
for /F "tokens=1* delims==" %%1 in ("!argv!") do (%\n%
set argv=%%1%\n%
set substring=%%2%\n%
)%\n%
for /F "tokens=1-3 delims=:~, " %%1 in ("!argv!") do (%\n%
set "value=!%%1!"%\n%
if not defined value set "value= "%\n%
set size=%%3%\n%
if not defined size (%\n%
if "%%2" == "-0" (%\n%
set "result=!value!!substring!"%\n%
) else (%\n%
if "!value:~%%2,1!" == "" (%\n%
for /L %%A in (1,1,%%2) do set "value=!value! "%\n%
set "result=!value:~0,%%2!!substring!"%\n%
) else (%\n%
set "result=!value:~0,%%2!!substring!!value:~%%2!"%\n%
)%\n%
)%\n%
) else (%\n%
if "%%2" == "-0" (%\n%
set "result=!value!!substring!"%\n%
) else if "!value:~%%2,1!" == "" (%\n%
for /L %%A in (1,1,%%2) do set "value=!value! "%\n%
set "result=!value:~0,%%2!!substring!"%\n%
) else if not "!size:~0,1!" == "-" (%\n%
set /A restart=%%2+%%3%\n%
for %%r in (!restart!) do set "result=!value:~0,%%2!!substring!!value:~%%r!"%\n%
) else (%\n%
set "result=!value:~0,%%2!!substring!"%\n%
if not "%%3" == "-0" set "result=!result!!value:~%%3!"%\n%
)%\n%
)%\n%
for %%v in ("!result!") do endlocal ^& set "%%1=%%~v"%\n%
)%\n%
) else setlocal enableDelayedExpansion ^& set argv=
for /F "skip=4 delims=pR tokens=1,2" %%a in (
'reg query hkcu\environment /v temp' ) do set TAB=%%b
setlocal EnableDelayedExpansion
cls
echo/
echo SET STRING=0123456789
echo/
for %%a in (0 4 -3 -0 13) do (
set string=0123456789
%set/s% string:~%%~a=abc
echo %%SET/S%% STRING:~%%~a=abc!TAB!!TAB!!string!
)
echo/
for %%a in ("0,3" "4,4") do (
set string=0123456789
%set/s% string:~%%~a=abc
echo %%SET/S%% STRING:~%%~a=abc!TAB!!TAB!!string!
)
for %%a in ("-3,1" "-0,2" "13,4") do (
set string=0123456789
%set/s% string:~%%~a=abc
echo %%SET/S%% STRING:~%%~a=abc!TAB!!string!
)
echo/
for %%a in ("0,-3" "4,-4" "-3,-1" "-0,-2" "13,-4") do (
set string=0123456789
%set/s% string:~%%~a=abc
echo %%SET/S%% STRING:~%%~a=abc!TAB!!string!
)
echo/
for %%a in ("0,-0" "4,-0" "-3,-0" "-0,-0" "13,-0") do (
set string=0123456789
%set/s% string:~%%~a=abc
echo %%SET/S%% STRING:~%%~a=abc!TAB!!string!
)
echo/
Code: Select all
SET STRING=0123456789
%SET/S% STRING:~0=abc abc0123456789
%SET/S% STRING:~4=abc 0123abc456789
%SET/S% STRING:~-3=abc 0123456abc789
%SET/S% STRING:~-0=abc 0123456789abc
%SET/S% STRING:~13=abc 0123456789 abc
%SET/S% STRING:~0,3=abc abc3456789
%SET/S% STRING:~4,4=abc 0123abc89
%SET/S% STRING:~-3,1=abc 0123456abc89
%SET/S% STRING:~-0,2=abc 0123456789abc
%SET/S% STRING:~13,4=abc 0123456789 abc
%SET/S% STRING:~0,-3=abc abc789
%SET/S% STRING:~4,-4=abc 0123abc6789
%SET/S% STRING:~-3,-1=abc 0123456abc9
%SET/S% STRING:~-0,-2=abc 0123456789abc
%SET/S% STRING:~13,-4=abc 0123456789 abc
%SET/S% STRING:~0,-0=abc abc
%SET/S% STRING:~4,-0=abc 0123abc
%SET/S% STRING:~-3,-0=abc 0123456abc
%SET/S% STRING:~-0,-0=abc 0123456789abc
%SET/S% STRING:~13,-0=abc 0123456789 abc
Code: Select all
%SET/A% variable=Func2(Param1,Param2)+var*Func1(Param)+234-Func0()
Code: Select all
:Func2 Param1 Param2 Result=
setlocal EnableDelayedExpansion
rem Do some calculation on Param1 and Param2, and get Result
rem . . .
endlocal & set %3=%result%
exit /B
Code: Select all
set /A "Param1=%~1"
set /A "Param2=%~2"
Code: Select all
%SET/A% variable=Func2(var3*var4,3*var5+6)+var*Func1(var1*2)+234-Func0()
This is SET/A macro:
Code: Select all
@echo off
::SET/A - Macro that expand function results in arithmetic expressions
::Antonio Perez Ayala
::Dec/26/2011 - v1.0 Parse functions, replace their return values, execute SET /A
setlocal DisableDelayedExpansion
set LF=^
::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
set set/a=for %%n in (1 2) do if %%n==2 (%\n%
rem 1- Store functions start and size in setA.F# array %\n%
for %%v in (N opPos exprLen) do set setA.%%v=0%\n%
for /L %%p in (12,-1,0) do (%\n%
set /A "setA.exprLen|=1<<%%p"%\n%
for %%l in (!setA.exprLen!) do if "!setA.expr:~%%l,1!"=="" set /A "setA.exprLen&=~1<<%%p"%\n%
)%\n%
set "setA.copyExpr=!setA.expr!"%\n%
for %%o in (+ - / %%%% ^^! ^^^> ^^^< ^^^& ^^^|) do set "setA.copyExpr=!setA.copyExpr:%%o=*!"%\n%
for %%v in (parenLevel namePos funcLevel) do set setA.%%v=%\n%
for /L %%c in (0,1,!setA.exprLen!) do (%\n%
set setA.char=!setA.copyExpr:~%%c,1!%\n%
if defined setA.funcLevel (%\n%
if "!setA.char!" == ")" (%\n%
set /A setA.funcLevel-=1%\n%
if !setA.funcLevel! == 0 (%\n%
set /A setA.N+=1, setA.funcLevel=%%c-setA.namePos%\n%
for %%n in (!setA.N!) do (%\n%
set "setA.F%%n=!setA.namePos! !setA.funcLevel!"%\n%
)%\n%
for %%v in (namePos funcLevel) do set setA.%%v=%\n%
rem noelse%\n%
)%\n%
) else if "!setA.char!" == "(" (%\n%
set /A setA.funcLevel+=1%\n%
)%\n%
) else if defined setA.namePos (%\n%
if "!setA.char!" == "(" (%\n%
set setA.funcLevel=1%\n%
) else (%\n%
if "!setA.char!" == "=" set setA.opPos=%%c^& set setA.namePos=%\n%
if "!setA.char!" == "*" set setA.opPos=%%c^& set setA.namePos=%\n%
if "!setA.char!" == "~" set setA.opPos=%%c^& set setA.namePos=%\n%
)%\n%
) else if defined setA.parenLevel (%\n%
if "!setA.char!" == ")" (%\n%
set /A setA.parenLevel-=1%\n%
if !setA.parenLevel! == 0 set setA.parenLevel=%\n%
) else if "!setA.char!" == "(" (%\n%
set /A setA.parenLevel+=1%\n%
)%\n%
) else if defined setA.opPos (%\n%
if "!setA.char!" == "(" (%\n%
set setA.parenLevel=1%\n%
set setA.opPos=%\n%
) else if not "!setA.char!" == " " (%\n%
set setA.namePos=%%c%\n%
set setA.opPos=%\n%
)%\n%
) else (%\n%
if "!setA.char!" == "=" set setA.opPos=%%c%\n%
if "!setA.char!" == "*" set setA.opPos=%%c%\n%
if "!setA.char!" == "~" set setA.opPos=%%c%\n%
)%\n%
)%\n%
rem 2- Call setA.F# functions and replace they by their return values %\n%
set setA.copyExpr=%\n%
set setA.lastStartCopy=0%\n%
for /L %%i in (1,1,!setA.N!) do (%\n%
for /F "tokens=1,2" %%a in ("!setA.F%%i!") do (%\n%
set setA.func=!setA.expr:~%%a,%%b!%\n%
set /A setA.lastSizeCopy=%%a-setA.lastStartCopy%\n%
set /A setA.newStartCopy=%%a+%%b+1%\n%
)%\n%
for /F "tokens=1* delims=(" %%a in ("!setA.func!") do call %%a %%b setA.R=%\n%
for /F "tokens=1,2" %%a in ("!setA.lastStartCopy! !setA.lastSizeCopy!") do (%\n%
set "setA.copyExpr=!setA.copyExpr!!setA.expr:~%%a,%%b!!setA.R!"%\n%
)%\n%
set setA.lastStartCopy=!setA.newStartCopy!%\n%
)%\n%
set /A setA.lastSizeCopy=setA.exprLen-setA.lastStartCopy+1%\n%
for /F "tokens=1,2" %%a in ("!setA.lastStartCopy! !setA.lastSizeCopy!") do (%\n%
set "setA.copyExpr=!setA.copyExpr!!setA.expr:~%%a,%%b!"%\n%
)%\n%
rem 3- Execute SET /A command on final expression %\n%
set /A !setA.copyExpr!%\n%
) else set setA.expr=
setlocal EnableDelayedExpansion
set "string=this has a length of 23"
%set/a% length=:StrLen(string)
echo The result is: %length%
%set/a% sum=:StrLen(string) + :StrLen("And this have 16")
echo The sum of two lengths is: %sum%
%set/a% result=:StrLen(String)*1000 / sum
echo The first lenght times 1000 divided by the previous sum is: %result%
goto :EOF
:StrLen string [result=[adjust]]
setlocal enableDelayedExpansion
set str=%1
set str=!str:"= !
if "!str:~0,1!" == " " (
set "str=0%~1"
) else (
set "str=0!%1!"
)
set len=0
for /L %%A in (12,-1,0) do (
set /A "len|=1<<%%A"
for %%B in (!len!) do if "!str:~%%B,1!" == "" set /A "len&=~1<<%%A"
)
for %%v in (!len!) do endlocal&if not "%2" == "" (set /A "%2=%%v%3") else echo %%v
exit /B
You must be aware that SET/A macro does NOT include the usual SETLOCAL/ENDLOCAL commands.Output wrote: The result is: 23
The sum of two lengths is: 39
The first lenght times 1000 divided by the previous sum is: 589
The original SET /A command may assign values to several variables in the same expression:
Code: Select all
SET /A var1=1*2+3, var2=123*456/321, var3=var1+var2
To avoid modification of local variables, all variable names used in SET/A macro have the "setA." prefix. However, if %set/a% macro would be used in a nested way, the function that create the nesting MUST include a SETLOCAL/ENDLOCAL pair, as described below.
EDIT: I slightly modified StrLen function so it can get the string both as variable name or "constant" in quotes.
Antonio