Batch "macros" with arguments

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Batch "macros" with arguments

#1 Post by dbenham » 18 May 2011 21:47

IMPORTANT UPDATE - See Macros with parameters appended for major improvements to the basic construction of a macro with arguments. But by all means, read this thread to get a basic understanding of macros along with many important macro concepts and design techniques.

All languages have time consuming overhead when calling a function. Unfortunately the time overhead for a batch function call can get quite large, especially when the batch file becomes large.

Many languages have the ability to avoid function call overhead via macros. It is common practice in "DOS" batch files to emulate a simple macro by storing commands in a variable, but typical implementations do not support arguments. DOSKEY supports true macros with arguments, but they can't be used in batch.

I've often wanted batch macros with arguments, but didn't think it was possible - until...
Ed Dyreen introduced a working emulation of macros with arguments in this thread SET /a -- Random Number! :D

I've been working with the basic syntax he developed, and I think it has a lot of potential, even for moderately complex algorithms. It's even possible to format the macro definitions and macro "calls" so they are relatively easy to read and understand.

I've prepared three sample macros that have identical functionality to existing functions. I also implemented timing routines as macros.

Not only are the macros faster, their performance should remain unchanged regardless of the size of the batch file.

Besides significant speed improvements over functions, the macros are very easy to use in multiple batch files. A library of macros can be defined by running a single batch file. Then any number of batch files can utilize the macros without worrying about including them in the file! :D

There are still some things to figure out:

- I think the macros that return numbers are good to go, but the toLower macro has issues with the FOR /F eol problem. We don't want any delimiter and the returned string could start with any character. I couldn't figure out how to use Jeb's technique of eol=<LF> in the macro.

- I'm wondering if a macro can call a macro? I haven't figured out a way to do this.

- Debugging is impacted and takes some getting used to. (I'm not there yet!)

- I imagine there are other issues yet to be discovered!

See my Batch "macros" with arguments - Major Update post on the 3rd page of this thread for significant updates to this batch project.

Code: Select all

@echo off
setlocal
 
::------------------------------------------
:: DEFINE MACROS
 
set callMacro=for /f "tokens=1-26" %%a in
 
set macroNum2Hex=do^
  setlocal enableDelayedExpansion^
  ^&(if defined hex set "hex=")^
  ^&set /a "dec=(%%~a)"^
  ^&set "map=0123456789ABCDEF"^
  ^&(for /l %%n in (1,1,8) do^
      set /a "d=dec&15,dec>>=4"^
      ^&for %%d in (!d!) do set "hex=!map:~%%d,1!!hex!"^
    )^
  ^&(for %%v in (!hex!) do endlocal^&if "%%~b" neq "" (set %%~b=%%v) else echo %%v)

set macroStrLen=do^
  setlocal enableDelayedExpansion^
  ^&set "str=A!%%~a!"^
  ^&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 "%%~b" neq "" (set %%~b=%%v) else echo %%v)

set macroToLower=do^
  setlocal enableDelayedExpansion^
  ^&set "str=!%%~a!"^
  ^&(for %%A in (^
     "A=a" "B=b" "C=c" "D=d" "E=e" "F=f" "G=g" "H=h" "I=i"^
    "J=j" "K=k" "L=l" "M=m" "N=n" "O=o" "P=p" "Q=q" "R=r"^
    "S=s" "T=t" "U=u" "V=v" "W=w" "X=x" "Y=y" "Z=z" "Ä=ä"^
    "Ö=ö" "Ü=ü"^
   ) do set "str=!str:%%~A!")^
  ^&(for /f "delims=" %%v in ("!str!") do endlocal^&set "%%~a=%%~v")

set macroGetTime=do^
  setlocal enableDelayedExpansion^
  ^&set "t=0"^
  ^&(for /f "tokens=1-4 delims=:." %%A in ("!time: =0!") do set /a "t=(((1%%A*60)+1%%B)*60+1%%C)*100+1%%D-36610100")^
  ^&(for %%v in (!t!) do endlocal^&set %%~a=%%v)

set macroDiffTime=do^
  setlocal enableDelayedExpansion^
  ^&set /a "DD=(%%~b)-(%%~a)"^
  ^&(if !DD! lss 0 set /a "DD+=24*60*60*100")^
  ^&set /a "HH=DD/360000, DD-=HH*360000, MM=DD/6000, DD-=MM*6000, SS=DD/100, DD-=SS*100"^
  ^&(if "!HH:~1!"=="" set "HH=0!HH!")^
  ^&(if "!MM:~1!"=="" set "MM=0!MM!")^
  ^&(if "!SS:~1!"=="" set "SS=0!SS!")^
  ^&(if "!DD:~1!"=="" set "DD=0!DD!")^
  ^&(for %%v in (!HH!:!MM!:!SS!.!DD!) do endlocal^&if "%%~c" neq "" (set %%~c=%%v) else echo %%v)

:: END MACROS
::------------------------------------------

set "txt=I Wonder HOW long this text string is?"

::==== Test Macros =============================
echo --------------------------
echo Macro results:
set result=
%callMacro% ("56789") %macroNum2Hex%
%callMacro% ("-1 result") %macroNum2Hex%
set result
%callMacro% ("t1") %macroGetTime%
(for /l %%n in (1,1,255) do %callMacro% ("%%n result") %macroNum2Hex%)
%callMacro% ("t2") %macroGetTime%
%callMacro% ("t1 t2 timeMacroNum2Hex") %macroDiffTime%
set result
set "result=%txt%"
%callMacro% ("t1") %macroGetTime%
(for /l %%n in (1,1,255) do %callMacro% ("result") %macroToLower%)
%callMacro% ("t2") %macroGetTime%
%callMacro% ("t1 t2 timeMacroToLower") %macroDiffTime%
set result
%callMacro% ("txt") %macroStrLen%
%callMacro% ("t1") %macroGetTime%
(for /l %%n in (1,1,255) do %callMacro% ("txt result") %macroStrLen%)
%callMacro% ("t2") %macroGetTime%
%callMacro% ("t1 t2 timeMacroStrLen") %macroDiffTime%
set result

::===== Test Function Calls ====================
echo --------------------------
echo Call results:
set result=
call :num2hex 56789
call :num2hex -1 result
set result
%callMacro% ("t1") %macroGetTime%
for /l %%n in (1,1,255) do call :num2hex %%n result
%callMacro% ("t2") %macroGetTime%
%callMacro% ("t1 t2 timeNum2Hex") %macroDiffTime%
set result
set "result=%txt%"
%callMacro% ("t1") %macroGetTime%
for /l %%n in (1,1,255) do call :toLower result
%callMacro% ("t2") %macroGetTime%
%callMacro% ("t1 t2 timeToLower") %macroDiffTime%
set result
call :strLen txt
%callMacro% ("t1") %macroGetTime%
for /l %%n in (1,1,255) do call :strLen txt result
%callMacro% ("t2") %macroGetTime%
%callMacro% ("t1 t2 timeStrLen") %macroDiffTime%
set result

::==== Show timings =============================
echo -------------------------
echo:num2Hex macro time x 255 = %timeMacroNum2Hex%
echo:         call time x 255 = %timeNum2Hex%
echo:
echo:toLower macro time x 255 = %timeMacroToLower%
echo:         call time x 255 = %timeToLower%
echo:
echo:strLen  macro time x 255 = %timeMacroStrLen%
echo:         call time x 255 = %timeStrLen%

exit /b

::------------------------------------------------------
:: Begin function definitions

:num2hex    NumVal [RtnVar]
  setlocal enabledelayedexpansion
  set /a dec=%~1 2>nul
  if defined hex set hex=
  set "map=0123456789ABCDEF"
  for /l %%n in (1,1,8) do (
      set /a "d=dec&15,dec>>=4"
      for %%d in (!d!) do set "hex=!map:~%%d,1!!hex!"
  )
  endlocal&if "%~2" neq "" (set %~2=%hex%) else echo:%hex%
exit /b

:strLen string len -- returns the length of a string
  setlocal enabledelayedexpansion
  set "str=A!%~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"
  )
  endlocal&if "%~2" neq "" (set /a %~2=%len%) else echo:%len%
exit /b

:toLower strVar
  setlocal enabledelayedexpansion
  set "str=!%~1!"
  for %%A in (
     "A=a" "B=b" "C=c" "D=d" "E=e" "F=f" "G=g" "H=h" "I=i"
    "J=j" "K=k" "L=l" "M=m" "N=n" "O=o" "P=p" "Q=q" "R=r"
    "S=s" "T=t" "U=u" "V=v" "W=w" "X=x" "Y=y" "Z=z" "Ä=ä"
    "Ö=ö" "Ü=ü"
   ) do set str=!str:%%~A!
  endlocal & set %~1=%str%
exit /b

results:

Code: Select all

--------------------------
Macro results:
0000DDD5
result=FFFFFFFF
result=000000FF
result=i wonder how long this text string is?
38
result=38
--------------------------
Call results:
0000DDD5
result=FFFFFFFF
result=000000FF
result=i wonder how long this text string is?
38
result=38
-------------------------
num2Hex macro time x 255 = 00:00:00.57
         call time x 255 = 00:00:02.19

toLower macro time x 255 = 00:00:00.71
         call time x 255 = 00:00:02.19

strLen  macro time x 255 = 00:00:00.71
         call time x 255 = 00:00:02.15


Dave Benham
Last edited by dbenham on 13 Jul 2013 10:45, edited 2 times in total.

jeb
Expert
Posts: 1041
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: Batch "macros" with arguments

#2 Post by jeb » 19 May 2011 02:53

dbenham wrote:- I think the macros that return numbers are good to go, but the toLower macro has issues with the FOR /F eol problem. We don't want any delimiter and the returned string could start with any character. I couldn't figure out how to use Jeb's technique of eol=<LF> in the macro.


You simply have to insert the <LF> in a way, so it expand to ^<LF><LF>


Code: Select all

set lf=^


set macroToLower=do^
  setlocal enableDelayedExpansion^
  ^&set "str=!%%~a!"^
  ^&(for %%A in (^
     "A=a" "B=b" "C=c" "D=d" "E=e" "F=f" "G=g" "H=h" "I=i"^
    "J=j" "K=k" "L=l" "M=m" "N=n" "O=o" "P=p" "Q=q" "R=r"^
    "S=s" "T=t" "U=u" "V=v" "W=w" "X=x" "Y=y" "Z=z" "Ä=ä"^
    "Ö=ö" "Ü=ü"^
   ) do set "str=!str:%%~A!")^
  ^&(for /f ^^^"eol^^=^^^%lf%%lf%^%lf%%lf%^^ delims^^=^^^" %%v in ("!str!") do endlocal^&set "%%~a=%%~v")


Btw.
You can use also the technic of the BatchLibrary prototype.
There I build something similiar to this macro technic.
But instead of build the macros with many & I put them into brackets, then you can use <LF> to create working new lines.
I used this for better debugging, and it looks better :)

Code: Select all

echo off
setlocal disableDelayedExpansion

::------------------------------------------
:: DEFINE MACROS
set lf=^


set ^"xlf=^^^%lf%%lf%^%lf%%lf%"
set ^"myMacro=do (%xlf%^
   setlocal enableDelayedExpansion %xlf%^
   set ^"str=%%~a^"%xlf%^
   echo !str!%xlf%^
)"
set "callMacro=for /f "tokens=1-26" %%a in"

:: - Test and show the code
echo on
%callMacro% ("hallo") %myMacro%


Output

Code: Select all

C:\temp>for /F "tokens=1-26" %a in ("hallo") do (
setlocal enableDelayedExpansion
 set "str=%~a"
 echo !str!
)

C:\temp>(
setlocal enableDelayedExpansion
 set "str=hallo"
 echo !str!
)
hallo

jeb

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: Batch "macros" with arguments

#3 Post by Ed Dyreen » 19 May 2011 05:55

[corrected]
@jeb; Nice, if it works I might adopt it, I have been avoiding the LF but maybe.
dbenham wrote:- I'm wondering if a macro can call a macro? I haven't figured out a way to do this.
Yes, next we have a macro to read &check a regkey, the checking is not done by the macro nooo, for that we use a macro !Err.Chk.IsDefined!

Code: Select all

            for %%? in ( ERR.Chk_deep.RegRead.TokenVAR ) do    set "%%~?=set "FullPathKey=^^^!%%~b^^^!"    ^&set "KeyName=^^^!%%~c^^^!"    ^&^>nul 2^>^&1 reg query "^^^!FullPathKey^^^!" /v "^^^!KeyName^^^!" ^&^&set /a ERR = 0 ^|^|set /a ERR = 1 ^&( if ^^^!ERR^^^! neq 0 ( %ERR.Set.THIS% "Reg query '^^^!FullPathKey^^^!' /v '^^^!KeyName^^^!'" ) else    for /f "skip=4 tokens=3* delims=   " %%§ in ( '2^^^>nul reg query "^^^!FullPathKey^^^!" /v "^^^!KeyName^^^!"' ) do set "Content=%%§" ^&set "%%~d=^^^!Content^^^!" ^&for %%^^^! in ( "%%~d" ) do !ERR.Chk.IsDefined.TokenSTR! %()% ^>nul    ) ^&( echo. ^&echo. ^&echo. RegRead    : '%%~d' ^&echo. FullPathKey: '^^^!FullPathKey^^^!' ^&echo. KeyName    : '^^^!KeyName^^^!' ^&set /p "?= Content    : '^^^!Content^^^!'" ^<nul ^&if ^^^!ERR^^^! neq 0 ( set /p "?= [ERROR:^^^!ERR^^^!]" ^<nul ) else set /p "?= [OK]" ^<nul ) "


            ::for /f "usebackq tokens=1-3 delims=¦" %%b in ( '"FullPathKeyVAR"¦"KeyNameVAR"¦"StoreVAR"' ) do %ERR.Chk_deep.RegRead.TokenVAR% %(>nul)%

            for %%? in ( ERR.Chk.IsDefined.TokenSTR ) do       set "%%~?=set /a ERR = 0 ^&( if not defined %%~^^^!    %ERR.Set.VALID% /not_defined: "%%~^^^!"    ) ^&(                   echo. ^&echo. ^&echo. IsDefined : '%%~^^^!' :    ^&set /p "?= '^^^!%%~^^^!^^^!'"    ^<nul ^&                             set /p "?= [OK]" ^<nul ) "

for %%! in ( "STR" ) do %ERR.Chk.IsDefined.TokenSTR% %(>nul)%
The question now is, how far can we push the limits.
We may or may not get environmental space problems as our macros grow in complexity, How many for commands can we nest &how deep can they be nested? Those are my concerns.
dbenham wrote:- Debugging is impacted and takes some getting used to. (I'm not there yet!)
Maybe this is where LF could come in handy.

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Batch "macros" with arguments

#4 Post by dbenham » 19 May 2011 11:40

Here is a shocking demonstration of the performance benefits of the macros. I ran the original batch file at the top of this thread on a shared network drive at my workplace. :shock: Below is the timing portion of the output:

Code: Select all

num2Hex macro time x 255 = 00:00:00.79
         call time x 255 = 00:00:25.81

toLower macro time x 255 = 00:00:00.80
         call time x 255 = 00:00:30.09

strLen  macro time x 255 = 00:00:00.79
         call time x 255 = 00:00:25.16

=============================================================
Jeb wrote:You simply have to insert the <LF> in a way, so it expand to ^<LF><LF>

Code: Select all

  ^&(for /f ^^^"eol^^=^^^%lf%%lf%^%lf%%lf%^^ delims^^=^^^" %%v in ("!str!") do endlocal^&set "%%~a=%%~v")


That's great Jeb! :D

I can see the need to use that cryptic bit of code in many macros. Can you figure out how to encapsulate the nasty bit in a variable so we can do the following :?: I tried and failed miserably:

Code: Select all

set forEntireLine= ????

set macroDef=...
  ^&(for /f %forEntireLine% %%v in ("!str!") do endlocal^&set "%%~a=%%~v")

I imagine it is easy using !LF!, but I'm pretty sure we don't want to rely on delayed expansion during the macro definition phase, especially if we want the macro definitions to persist after the batch completes.

========================================================
Jeb wrote:But instead of build the macros with many & I put them into brackets, then you can use <LF> to create working new lines.
I used this for better debugging, and it looks better

I'm sold. That looks great. :D

========================================================
Ed Dyreen wrote:
dbenham wrote:- I'm wondering if a macro can call a macro? I haven't figured out a way to do this.

Yes,
Next we have a macro to read &check a regkey, the checking is not done by the macro nooo, for that we use a macro !Err.Chk.IsDefined!

I can't follow your code. :? It is dependent on at least one undefined variable/macro (%ERR.Set.VALID%). I also suspect you are including some other abstract concept like objects that is further confusing me. Is the trick to use delayed expansion when calling a macro from a macro? Can you provide a small working example that focuses on a macro calling a macro :?:

=========================================================

Dave Benham

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: Batch "macros" with arguments

#5 Post by Ed Dyreen » 19 May 2011 12:48

Err.Set.Valid has to be removed ! You don't have that function & it is not required!

I am still experimenting with DelayedExpansion, after that I will look in your methods because again I have to admit I never used the LF technique, also I am quite inexperienced with running a batch in disabled DelayedExpansion.

I am seeing some weird things going on with DelayedExpansion;

-I can define a macro which uses %%b as an inputtoken, but I cannot redine %%b or an undefined %%z as a new inputtoken for another macro, the only token that seems to work is ! namely %%^^^! if part of the macro.

We need to get around this, it is a problem with or without using delayed expansion ! It does makes sense though that we can't tokenize a for within a tokenized for loop.

This limits the number of macros I can nest to exactly 2. One for alphanumeric tokens &two for a special token %%^^^!

I feel you are making better progress than I, I feel the DelayedExpansion is going to prove a nogo area :cry:
I've tested this code you don't even need to remove %Err.Set.This% it works as is. Here's your demo :

Code: Select all

@echo off &SetLocal EnableExtensions EnableDelayedExpansion
echo.
echo.
set "FullPath.LocalHost.SCRIPT=C:\JustADemo\JustADemo.DEM"

echo. Post.define: 'Create.FileName' [OK]
::for %%! in ( Initialize "FileName.String" "StoreVariable.String" "Error.bool" ) do %Create.Filename% %(>nul)%
for %%? in ( Create.FileName ) do set "%%~?=if /i ["%%~^^^!"] == ["Initialize"] ( set /a $ = 0 ) else set /a $ += 1 ^&if ^^^!$^^^! equ 1 ( set "StoreVariable.String=%%~^^^!" ) else if ^^^!$^^^! equ 2 ( set "FileName.String=%%~^^^!" ) else set "Error.bool=%%~^^^!" ^&for %%^^^! in ( "^^^!FileName.String^^^!" ) do set "FileName.Variable=%%~n^^^!" ^&echo.^^^!FileName.String^^^! ^|^>nul FindStr.EXE /i /c:"^^^!FileName.Variable^^^!" ^&^&set /a Error.Integer = 0 ^|^|set /a Error.Integer = 1 ^&( if ^^^!Error.Integer^^^! neq 0 ( if ^^^!Error.bool^^^! neq 0 %ERR.Set.THIS% "Not a valid FileName : '^^^!FileName.String^^^!'" ) else set "^^^!StoreVariable.String^^^!.Filename=^^^!FileName.Variable^^^!" ) ^&echo. ^&echo. ^&set /p "?= ^^^!StoreVariable.String^^^!.Filename : '^^^!FileName.Variable^^^!' [OK]" ^<nul"

echo. Pre.define: 'Create.FileName' [OK]

echo. Post.define: 'Create.Object' [OK]
::for /f "usebackq tokens=1-4 delims=¦" %%b in ( '"Object.Name"¦"Object.Extension"¦"Object.Data"¦"Error.bool"' ) do %Create.Object% %(>nul)%
for %%? in ( Create.Object ) do set "%%~?=set "Object.Name=%%~b" ^&set "Object.Extension=%%~c" ^&set "Object.Data=%%~d" ^&set "Error.bool=%%~e" ^&echo. ^&echo. Object.Name     :^^^!Object.Name^^^!_ ^&echo. Object.Extension:^^^!Object.Extension^^^!_ ^&echo. Object.Data     :^^^!Object.Data^^^!_ ^&echo. Error.bool      :^^^!Error.bool^^^!_ ^&if /i ["^^^!Object.Extension^^^!"] == ["Filename"] (  echo. Creating Limb   :^^^!Object.Name^^^!.Filename  ^&for %%^^^! in ( Initialize "^^^!Object.Name^^^!" "^^^!Object.Data^^^!" "^^^!Error.bool^^^!" ) do !Create.Filename! %()% )"


echo. Pre.define: 'Create.Object' [OK]

echo. Pre Execution Create.Object.FileName [OK]
for /f "usebackq tokens=1-4 delims=¦" %%b in ( '"Host"¦"FileName"¦"!FullPath.LocalHost.SCRIPT!"¦"1"' ) do %Create.Object%

echo.
echo.
echo. Post Execution Create.Object.FileName [OK]

echo.endoftest
pause
exit
Instead of using tokens maybe variables :idea:

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Batch "macros" with arguments

#6 Post by dbenham » 19 May 2011 14:11

dbenham wrote:That's great Jeb!

I can see the need to use that cryptic bit of code in many macros. Can you figure out how to encapsulate the nasty bit in a variable so we can do the following? I tried and failed miserably:

Code: Select all

set forEntireLine= ????

set macroDef=...
  ^&(for /f %forEntireLine% %%v in ("!str!") do endlocal^&set "%%~a=%%~v")

Never mind Jeb! I figured it out after all. :D

Code: Select all

set lf=^


set forEntireLine=^^^^^^^"eol^^^^=^^^^^^^%lf%%lf%^%lf%%lf%^^^%lf%%lf%^%lf%%lf%^^^^ delims^^^^=^^^^^^^"

set test1=(for /f ^^^"eol^^=^^^%lf%%lf%^%lf%%lf%^^ delims^^=^^^" %%v in ("!str!") do endlocal^&set "%%~a=%%~v")

set test2=(for /f %forEntireLine% %%v in ("!str!") do endlocal^&set "%%~a=%%~v")

set test

output:

Code: Select all

test1=(for /f ^"eol^=^

^ delims^=^" %v in ("!str!") do endlocal&set "%~a=%~v")
test2=(for /f ^"eol^=^

^ delims^=^" %v in ("!str!") do endlocal&set "%~a=%~v")



@Ed - I'll look at your example later - Thanks

Dave Benham

jeb
Expert
Posts: 1041
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: Batch "macros" with arguments

#7 Post by jeb » 19 May 2011 14:22

Ed Dyreen wrote:I feel you are making better progress than I, I feel the DelayedExpansion is going to prove a nogo area :cry:


Perhaps you got problems with the hidden "feature" of the delayed expansion, the double escaping for the caret "^", but only if there is at least one exclamation mark in your line.
The first escape is only work outside of quotes, the second escape phase isn't interesting in quotes.

Code: Select all

setlocal EnableDelayedExpansion
set var1a=1^  2^^ 3^^^ 4^^^^
set var1b=1^  2^^ 3^^^ 4^^^^!
set "var2a=1^  2^^ 3^^^ 4^^^^"
set "var2b=1^  2^^ 3^^^ 4^^^^!"
set var


Output

Code: Select all

var1a=1  2^ 3^ 4^^
var1b=1  2 3 4^
var2a=1^  2^^ 3^^^ 4^^^^
var2b=1  2^ 3^ 4^^


Ed Dyreen wrote:-I can define a macro which uses %%b as an inputtoken, but I cannot redine %%b or an undefined %%z as a new inputtoken for another macro, the only token that seems to work is ! namely %%^^^! if part of the macro.


Do you can show a small expample :?:

jeb

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: Batch "macros" with arguments

#8 Post by Ed Dyreen » 19 May 2011 14:53

'
Euh no!, if I make it any smaller it doesn't works any more &you can't see what does work about it. :shock:

Where can I find some in depth information on the LF and EOL tricks.

what does xlf do in set ^"xlf=^^^%lf%%lf%^%lf%%lf%" ?
why is the first quote escaped, is there an inbalance in quotes, I don't understand :shock:

jeb
Expert
Posts: 1041
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: Batch "macros" with arguments

#9 Post by jeb » 19 May 2011 16:33

Ed Dyreen wrote:Where can I find some in depth information on the LF and EOL tricks.

Somewhere, sometime I have posted something about that, but I can't remember where :(

But there are only a few simple rules.
A percent-expanded <LF> character in a normal line stops the parser.
A percent-expanded <LF> in a bracket starts a new command line
A delayed or %%var expanded <LF> is handled as normal character
To escape a LF with a caret there is the rule:
The first <LF> after a caret is ignored, the next character after the <LF> is escaped even if this is also a <LF>
Therefore you need two empty lines to create a LF, the <LF> after the caret is removed, the next <LF> is escaped, so the second line doesn't have an end, therefore you need the second empty line, else you append rubbish.

Code: Select all

set LF=^


rem ** Two empty lines are neccessary
set recreateLF=^%LF%%LF%
REM The contents of the variables recreateLF and LF are both a single linefeed
setlocal EnableDelayedExpansion
echo 1a: Line1!LF!1b: Line2
echo 2a: hallo%LF%2b: this is lost
(
echo 3a: hallo%lf%echo 3b: this is a legal command
)
set var1=this!LF!works
set "var2=this!LF!works too"
set var3=this%LF%fails
set "var4=this%LF%fails too"
set var5=this^%LF%%LF%works
set "var6=this^%LF%%LF%fails"
set ^"var7=this^%LF%%LF%works again"
set var

Output

Code: Select all

1a: Line1
1b: Line2
2a: hallo
3a: hallo
3b: this is a legal command
var1=this
works
var2=this
works too
var3=this
var4=this
var5=this
works
var6=this^
var7=this
works again


Ed Dyreen wrote:what does xlf do in set ^"xlf=^^^%lf%%lf%^%lf%%lf%" ?

xlf is the variable name, the name isn't important.
The content of XLF is "^<LF><LF>" (3-characters) so you can use it with percent-expansion.
echo Line1%XLF%Line2

Ed Dyreen wrote:why is the first quote escaped, is there an inbalance in quotes, I don't understand :shock:

No it isn't unbalanced, I could also write set xlf=^^^%lf%%lf%^%lf%%lf% but I prefer quotes to ensure a proper end without "hidden" spaces.
But here a normal quote would fail, as it quotes the carets and then the <LF>s aren't escaped anymore, therefore I escape the first quote.

jeb

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: Batch "macros" with arguments

#10 Post by Ed Dyreen » 19 May 2011 17:03

'
set xlf=^^^%lf%%lf%^%lf%%lf%
So xlf should only be used for macros ?

I am currently working on this, but you don't need to run the code to see what my problem is getting the value back over endlocal :

Code: Select all

::------------------------------------------------------------
:: Pre Define Create.Object             >>
::
set ^"Create.Object=do (             %ELF%^

   Setlocal EnableDelayedExpansion       %ELF%^

   set ^"Object.Name=%%~a^"          %ELF%^
   set ^"Object.Extension=%%~b^"          %ELF%^
   set ^"Object.Data=%%~c^"          %ELF%^
   set ^"Error.bool=%%~d^"          %ELF%^
   echo.                   %ELF%^
   echo. Object.Name     :!Object.Name!_       %ELF%^
   echo. Object.Extension:!Object.Extension!_    %ELF%^
   echo. Object.Data     :!Object.Data!_       %ELF%^
   echo. Error.bool      :!Error.bool!_       %ELF%^

   Endlocal                %ELF%^

   set ^"Object.Name=%Object.Name%_^"       %ELF%^
   set ^"Object.Extension=%Object.Extension%_^"    %ELF%^
   set ^"Object.Data=%Object.Data%_^"       %ELF%^
   set ^"Error.bool=%Error.bool%_^"       %ELF%^
)"
::
:: Post Define Create.Object             <<
::------------------------------------------------------------
I know how to do this, but it requires a function call after endlocal, the technique is :

Code: Select all

endlocal &call :function << on the same line, that is important or between ()

:function ()
setlocal
::(
     ::do some stuff
::
(
     endlocal

     set var=%var%
)
:goto :eof ()
::)
not very convinient for a macro :x

What is possible I think is writing the value to the registry before endlocal &reading it after endlocal within the same macro. but it is quite an overhead! Or using set /p ? =< "", but then we need a file! Also quite an overhead!

I need a brilliant trick :?
Last edited by Ed Dyreen on 19 May 2011 17:27, edited 2 times in total.

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Batch "macros" with arguments

#11 Post by dbenham » 19 May 2011 17:25

I've refined Jeb's %XLF%^ syntax by including the ^ in the definition and renaming it to %\n%.

I've taken everything we have learned so far and folded it into the originally posted code. I really like how these macros look and behave! 8)

Code: Select all

@echo off
setlocal

::------------------------------------------
:: DEFINE MACROS

set LF=^


::Above 2 blank lines are required - do not remove

set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

set forEntireLine=^^^^^^^"eol^^^^=^^^^^^^%LF%%LF%^%LF%%LF%^^^%LF%%LF%^%LF%%LF%^^^^ delims^^^^=^^^^^^^"

set callMacro=for /f "tokens=1-26" %%a in

set macroNum2Hex=do (%\n%
  setlocal enableDelayedExpansion%\n%
  if defined hex set "hex="%\n%
  set /a "dec=(%%~a)"%\n%
  set "map=0123456789ABCDEF"%\n%
  for /l %%n in (1,1,8) do (%\n%
    set /a "d=dec&15,dec>>=4"%\n%
    for %%d in (!d!) do set "hex=!map:~%%d,1!!hex!"%\n%
  )%\n%
  for %%v in (!hex!) do endlocal^&if "%%~b" neq "" (set "%%~b=%%v") else echo %%v%\n%
)

set macroStrLen=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set "str=A!%%~a!"%\n%
  set "len=0"%\n%
  for /l %%A in (12,-1,0) do (%\n%
    set /a "len|=1<<%%A"%\n%
    for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"%\n%
  )%\n%
  for %%v in (!len!) do endlocal^&if "%%~b" neq "" (set "%%~b=%%v") else echo %%v%\n%
)

set macroToLower=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set "str=!%%~a!"%\n%
  for %%A in (%\n%
    "A=a" "B=b" "C=c" "D=d" "E=e" "F=f" "G=g" "H=h" "I=i"%\n%
    "J=j" "K=k" "L=l" "M=m" "N=n" "O=o" "P=p" "Q=q" "R=r"%\n%
    "S=s" "T=t" "U=u" "V=v" "W=w" "X=x" "Y=y" "Z=z" "Ä=ä"%\n%
    "Ö=ö" "Ü=ü"%\n%
  ) do set "str=!str:%%~A!"%\n%
  for /f %forEntireLine% %%v in ("!str!") do endlocal^&set "%%~a=%%~v"%\n%
)

set macroGetTime=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set "t=0"%\n%
  for /f "tokens=1-4 delims=:." %%A in ("!time: =0!") do set /a "t=(((1%%A*60)+1%%B)*60+1%%C)*100+1%%D-36610100"%\n%
  for %%v in (!t!) do endlocal^&set "%%~a=%%v"%\n%
)

set macroDiffTime=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set /a "DD=(%%~b)-(%%~a)"%\n%
  if !DD! lss 0 set /a "DD+=24*60*60*100"%\n%
  set /a "HH=DD/360000, DD-=HH*360000, MM=DD/6000, DD-=MM*6000, SS=DD/100, DD-=SS*100"%\n%
  if "!HH:~1!"=="" set "HH=0!HH!"%\n%
  if "!MM:~1!"=="" set "MM=0!MM!"%\n%
  if "!SS:~1!"=="" set "SS=0!SS!"%\n%
  if "!DD:~1!"=="" set "DD=0!DD!"%\n%
  for %%v in (!HH!:!MM!:!SS!.!DD!) do endlocal^&if "%%~c" neq "" (set "%%~c=%%v") else echo %%v%\n%
)

:: END MACROS
::------------------------------------------

set "txt=;I Wonder HOW long this text string is?"

::==== Test Macros =============================
echo --------------------------
echo Macro results:
set result=
%callMacro% ("56789") %macroNum2Hex%
%callMacro% ("-1 result") %macroNum2Hex%
set result
%callMacro% ("t1") %macroGetTime%
for /l %%n in (1,1,255) do %callMacro% ("%%n result") %macroNum2Hex%
%callMacro% ("t2") %macroGetTime%
%callMacro% ("t1 t2 timeMacroNum2Hex") %macroDiffTime%
set result
set "result=%txt%"
%callMacro% ("t1") %macroGetTime%
for /l %%n in (1,1,255) do %callMacro% ("result") %macroToLower%
%callMacro% ("t2") %macroGetTime%
%callMacro% ("t1 t2 timeMacroToLower") %macroDiffTime%
set result
%callMacro% ("txt") %macroStrLen%
%callMacro% ("t1") %macroGetTime%
for /l %%n in (1,1,255) do %callMacro% ("txt result") %macroStrLen%
%callMacro% ("t2") %macroGetTime%
%callMacro% ("t1 t2 timeMacroStrLen") %macroDiffTime%
set result

::===== Test Function Calls ====================
echo --------------------------
echo Call results:
set result=
call :num2hex 56789
call :num2hex -1 result
set result
%callMacro% ("t1") %macroGetTime%
for /l %%n in (1,1,255) do call :num2hex %%n result
%callMacro% ("t2") %macroGetTime%
%callMacro% ("t1 t2 timeNum2Hex") %macroDiffTime%
set result
set "result=%txt%"
%callMacro% ("t1") %macroGetTime%
for /l %%n in (1,1,255) do call :toLower result
%callMacro% ("t2") %macroGetTime%
%callMacro% ("t1 t2 timeToLower") %macroDiffTime%
set result
call :strLen txt
%callMacro% ("t1") %macroGetTime%
for /l %%n in (1,1,255) do call :strLen txt result
%callMacro% ("t2") %macroGetTime%
%callMacro% ("t1 t2 timeStrLen") %macroDiffTime%
set result

::==== Show timings =============================
echo -------------------------
echo:num2Hex macro time x 255 = %timeMacroNum2Hex%
echo:         call time x 255 = %timeNum2Hex%
echo:
echo:toLower macro time x 255 = %timeMacroToLower%
echo:         call time x 255 = %timeToLower%
echo:
echo:strLen  macro time x 255 = %timeMacroStrLen%
echo:         call time x 255 = %timeStrLen%

exit /b

::------------------------------------------------------
:: Begin function definitions

:num2hex    NumVal [RtnVar]
  setlocal enabledelayedexpansion
  set /a dec=%~1 2>nul
  if defined hex set hex=
  set "map=0123456789ABCDEF"
  for /l %%n in (1,1,8) do (
      set /a "d=dec&15,dec>>=4"
      for %%d in (!d!) do set "hex=!map:~%%d,1!!hex!"
  )
  endlocal&if "%~2" neq "" (set %~2=%hex%) else echo:%hex%
exit /b

:strLen string len -- returns the length of a string
  setlocal enabledelayedexpansion
  set "str=A!%~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"
  )
  endlocal&if "%~2" neq "" (set /a %~2=%len%) else echo:%len%
exit /b

:toLower strVar
  setlocal enabledelayedexpansion
  set "str=!%~1!"
  for %%A in (
    "A=a" "B=b" "C=c" "D=d" "E=e" "F=f" "G=g" "H=h" "I=i"
    "J=j" "K=k" "L=l" "M=m" "N=n" "O=o" "P=p" "Q=q" "R=r"
    "S=s" "T=t" "U=u" "V=v" "W=w" "X=x" "Y=y" "Z=z" "Ä=ä"
    "Ö=ö" "Ü=ü"
   ) do set str=!str:%%~A!
  endlocal & set %~1=%str%
exit /b


Dave Benham

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: Batch "macros" with arguments

#12 Post by Ed Dyreen » 19 May 2011 17:32

'
But you are avoiding variables..! :|

Code: Select all

   Endlocal %\n%
   set ^"Object.Name=%Object.Name%_^" %\n%
That's easy

Code: Select all

   set ^"Object.Name=%%~a_^" %\n%
Also I still can't pass a token other than ! to another macro from within the macro, would it be possible to pass token ! endlessly between macros? %%a is not gonna work as we are already using it to call our first macro!

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Batch "macros" with arguments

#13 Post by dbenham » 19 May 2011 19:32

From 3 posts ago:
Ed Dyreen wrote:I am currently working on this, but you don't need to run the code to see what my problem is getting the value back over endlocal :
<code removed>
I know how to do this, but it requires a function call after endlocal...
<code removed>
[but it is not] not very convinient for a macro

What is possible I think is writing the value to the registry before endlocal &reading it after endlocal within the same macro. but it is quite an overhead! Or using set /p ? =< "", but then we need a file! Also quite an overhead!

I need a brilliant trick!

Sorry Ed - my prior post was not in response to your post, I was just summing up where we stand.
That being said, the answer is in my last post. Jeb showed me how to use a for loop to pass a value over the endlocal boundry in a prior topic. In my code examples, the for loop variable %%v does the trick.

In Ed's most recent post:
Ed Dyreen wrote:But you are avoiding variables..!

I don't understand what you mean.

Ed Dyreen wrote:Also I still can't pass a token other than ! to another macro from within the macro, would it be possible to pass token ! endlessly between macros? %%a is not gonna work as we are already using it to call our first macro!

I haven't looked at your macro calling macro example yet. But we can re-use FOR variables within a macro! Take a look at this:

Code: Select all


@echo off
setlocal

set LF=^


::Above 2 blank lines are required - do not remove

set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

set callMacro=for /f "tokens=1-26" %%a in

set macro=do (%\n%
  setLocal enableDelayedExpansion %\n%
  echo in macro prior to loop: a=%%a %\n%
  for /l %%a in (1,1,3) do echo inside inner macro loop: a=%%a %\n%
  echo in macro after loop: a=%%a %\n%
  set "rtn=Return Value" %\n%
  echo variable in macro to return: rtn=!rtn! %\n%
  for /f "delims=" %%v in ("!rtn!") do set "%%~a=%%v" %\n%
)

set macro

set var=
echo before call to macro: var=%var%
%callMacro% ("var") %macro%
echo after call to macro: var=%var%

Output:

Code: Select all

macro=do (
  setLocal enableDelayedExpansion
  echo in macro prior to loop: a=%a
  for /l %a in (1,1,3) do echo inside inner macro loop: a=%a
  echo in macro after loop: a=%a
  set "rtn=Return Value"
  echo variable in macro to return: rtn=!rtn!
  for /f "delims=" %v in ("!rtn!") do set "%~a=%v"
)
before call to macro: var=
in macro prior to loop: a=var
inside inner macro loop: a=1
inside inner macro loop: a=2
inside inner macro loop: a=3
in macro after loop: a=var
variable in macro to return: rtn=Return Value
after call to macro: var=Return Value


Dave Benham

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: Batch "macros" with arguments

#14 Post by Ed Dyreen » 19 May 2011 20:40

'
Not really a macro calll from a macro but more like a macro expansion within a macro.

Code: Select all

::------------------------------------------------------------------
:: Pre Define Create.FileName                >>
::
set Create.FileName=( echo. Object.StdIn=%%~a_ ^^^&echo. Object.StdOut=%%~b_ ^^^&echo. Object.Error=%%~c_ ^^^&set Object.StdOut=%%~na_ )
::
:: Post Define Create.FileName                <<
::------------------------------------------------------------------

::----------------------------------------------------------------------------------
:: Pre Define Create.Object                      >>
::
set Create.Object=do (                         %\n%

   Setlocal EnableDelayedExpansion                %\n%

   set "Object.Name=%%~a"                      %\n%
   set "Object.Extension=%%~b"                   %\n%
   set "Object.StdIn=%%~c"                   %\n%
   set "Object.StdOut=return"                   %\n%
   set "Object.Error=%%~d"                   %\n%
   echo. %\n%

   echo. Create.Object : %\n%
   echo.  Object.Name     :!Object.Name!_ %\n%
   echo.  Object.Extension:!Object.Extension!_ %\n%
   echo.  Object.StdIn    :!Object.StdIn!_ %\n%
   echo.  Object.Error    :!Object.Error!_ %\n%

   set "Create.Object.Extension=%%Create.!Object.Extension!%%"       %\n%

    %Call_aToken_Macro% ( '"!Object.StdIn!"¦"!Object.StdOut!"¦"1"' ) do %Create.FileName% %\n%
    for %%a in ("hello from token") do echo. %%~a %\n%
   for %%r in ("!Object.StdOut!") do (                %\n%

      Endlocal                      %\n%
      if "%%~a.%%~b" neq "." set "%%~a.%%~b=%%~r"          %\n%
      echo.  Object.StdOut: %%~a.%%~b : '%%~r' %\n%
   )                            %\n%
)
::
:: Post Define Create.Object                      <<
::----------------------------------------------------------------------------------
can i combine the linefeed method with EnableDelayedExpansion while defining a macro :?:

jeb
Expert
Posts: 1041
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: Batch "macros" with arguments

#15 Post by jeb » 20 May 2011 03:52

Ed Dyreen wrote:Not really a macro calll from a macro but more like a macro expansion within a macro.

I can't see a way of a really direct executing/calling a macro from within a macro, without instant expansion of the macros.

Ed Dyreen wrote:can i combine the linefeed method with EnableDelayedExpansion while defining a macro :?:

Yes, you should be able to use the %\n% technic as is, but you have to escape all your !Object.Name! ... definitions to
^!Object.Name^!

jeb

Post Reply