Batch "macros" with arguments

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

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

Re: Batch "macros" with arguments

#16 Post by Ed Dyreen » 20 May 2011 06:51

Really, howcome its not working :?:

Code: Select all

::for %%! in ( "VAR" ) do %IsDefined.Error% %(>nul)%
::
for %%? in ( IsDefined.Error ) do set "%%~?=set /a ERR = 0 ^&( if not defined %%~^^^! set /a ERR = 1 ) ^&( echo. ^&echo. ^&echo. IsDefined : '%%~^^^!' : ^&set /p "?= '^^^!%%~^^^!^^^!'" ^<nul ^&if ^^^!ERR^^^! neq 0 ( set /p "?= [ERROR:^^^!ERR^^^!]" ^<nul ) else    set /p "?= [OK]" ^<nul )"

set VAR=some

for %%! in ( "VAR" ) do %IsDefined.Error% %(>nul)%
echo.
echo.algorythm:
echo.!IsDefined.Error!_


for %%? in ( IsDefined.Error ) do set "%%~?=( %\n%
echo.hello)"

for %%! in ( "VAR" ) do %IsDefined.Error% %(>nul)%
echo.
echo.algorythm:
echo.!IsDefined.Error!_

echo.endoftest
pause
exit

Code: Select all

 IsDefined : 'VAR' :
 'some' [OK]
algorythm:
set /a ERR = 0 &( if not defined %~^! set /a ERR = 1 ) &( echo. &echo. &echo. Is
Defined : '%~^!' : &set /p "?= '!%~!!'" <nul &if ^!ERR^! neq 0 ( set /p "?= [ERR
OR:!ERR!]" <nul ) else  set /p "?= [OK]" <nul )_
hello)"
De syntaxis van de opdracht is onjuist.
Z:\ADMIN\REPAIR\OS\ED's_WinXPPro32x86\EDInstall_WinXPPro32x86 beta v1>
With EnabledDelayedExpansion I need to see &check my macro before I execute it!
Last edited by Ed Dyreen on 20 May 2011 07:14, edited 3 times in total.

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

Re: Batch "macros" with arguments

#17 Post by dbenham » 20 May 2011 06:59

OK - I have finally caught up with the macro "calling" macro discussion. Thanks Jeb and Ed, it's been a good exchange. :D

I had been hoping that the expansion of the inner macro could be delayed until run-time because, as Ed pointed out, I was concerned about the size limit of any single macro definition. But I now realize that there is no avoiding the fact that at run time the entire fully expanded macro must exist in memory as one parsed "statement", so it was a fools dream.

So we agree that the macro must be fully expanded during definition. But what is the best way to do that.

I see how Ed is able to accomplish this with simple calls using delayed expansion. But I very much want to avoid delayed expansion during macro definition because:

1) I want the macro definitions to persist after the batch that defines them terminates.
2) Delayed expansion complicates the syntax of the definition for the reasons that Jeb has pointed out.

:idea: I am working on a 2 part macro definition mechansim. First the macro is defined with embedded macros referenced in some way that will not be expanded but that can be identified as a macro reference. Something like ${macroName}. Next I call a derefMacro function that recursively expands the ${macroName} references. The derefMacro function will need delayed expansion, but all of the complicated logic will be encapsulated in one place where it can be reused for many macro definitions. I'll post the results of my experiments later.

Dave Benham

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

Re: Batch "macros" with arguments

#18 Post by Ed Dyreen » 20 May 2011 07:08

1) I want the macro definitions to persist after the batch that defines them terminates.
What do u mean, If the current DOS environment closes, there is nothing to persist in!

I make the environment persist like :

Code: Select all

>"%%~!" echo.@start "Restart" /low /min %%comspec%% /!COMMAND! "!FullPath.LocalHost.SCRIPT!\!File.SCRIPT!" ||echo.whooptiddy
start "!TITLE!" /low /min /wait /D "!SystemROOT!\system32" "%comspec%" /c "%%~!"
Exit 0
Can someone help me with my previous post? I really wan't to use \n with EnaDelayExpansion

No wait this works ...

Code: Select all

set ^"var7=this^%LF%%LF%works again"

echo.!var7!
echo.endoftest
pause
exit

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

Re: Batch "macros" with arguments

#19 Post by dbenham » 20 May 2011 09:34

Ed Dyreen wrote:What do u mean, If the current DOS environment closes, there is nothing to persist in!

Just because the batch terminates doesn't mean the parent "DOS" session has to terminate. I've been meaning to ask why you always terminate your example code with exit - this practice indeed terminates the DOS session.

I want to be able to run a batch file that loads a library of macros and then terminates with the DOS session still alive. The macro library file might end with exit /b, but not exit. At this point the macros are defined and available to any batch file that might be run subsequently.

Dave Benham

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

Re: Batch "macros" with arguments

#20 Post by Ed Dyreen » 20 May 2011 09:44

'
REALLY !, I did not know that . 8)
But have been progressing with macros &enableddelayedexpansion, macro1 could be called directly or indirectly by another macro:

Code: Select all

::------------------------------------------------------------
::Pre Define LocalScope EnableDelayedExpansion
::                      >>
::(
   Setlocal EnableDelayedExpansion

   ::Recreate single LineFeed once
   set \n=^%LF%%LF%

   ::Recreate single LineFeed twice
   set \n2=^^^%lf%%lf%^%lf%%lf%
::)
::
::Post Define LocalScope EnableDelayedExpansion       <<
::------------------------------------------------------------

::----------------------------------------------------------------------------------
:: Pre Define See.Error                      >>
::
::(
   set ^"See.Error=(    if ^^^!Error^^^! neq 0 ( %\n2%^

               set /p "?= [Error:^!Error^!]" ^<nul %\n2%^

            ) else    set /p "?= [OK:^!Error^!]" ^<nul %\n2%^
         )"
::)
::
:: Post Define See.Error                      <<
::----------------------------------------------------------------------------------

::----------------------------------------------------------------------------------
:: Pre Define IsDefined.Error                      >>
::
::(
   set ^"IsDefined.Error=do ( %\n2%^

      Setlocal EnableDelayedExpansion %\n2%^

      if defined %%~^^^! ( set /a Error = 0 ) else set /a Error = 1 %\n2%^

      ( %\n2%^
         echo. %\n2%^
         echo. %\n2%^
         echo. IsDefined : '%%~^^^!' : %\n2%^
         set /p "?= '^!%%~^!^!'" ^<nul %\n2%^

         !See.Error! %\n2%^

      ) %\n2%^
   )"
::)
::
:: Post Define IsDefined.Error                      <<
::----------------------------------------------------------------------------------

::------------------------------------------------------------
:: - Test.Macro >>
::
::(

   set "Test.Macro=!IsDefined.Error!"
   ::
   echo.!Test.Macro!_
   ::
   set "var=filled"
   ::
   for %%! in ( "VAR" ) %Test.Macro% %(>nul)%
   ::
   echo.
   echo.endoftest
   pause
   exit
::)
It looks promising and is exactly what I wanted.

Also I am wondering if there would be a performance benefit if I left out all setlocal definitions, it is no problem if i use strict naming conventions for variable names.

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

Re: Batch "macros" with arguments

#21 Post by dbenham » 20 May 2011 17:15

Yipee! :!: :D

I've succeeded in implementing a macro calling a macro two levels deep. I used the drefMacro function I talked about in my last post.

Warning - do not recurse macro definitions :!:

Known bug: :? Jeb - I attempted to utilize a modified version of the special return beyond ENDLOCAL technique you developed in New Functions - :asc, :chr, :str2Hex, :hex2Str. But I ran into problems when the returning for loop used a variable that matched a variable in a macro def. I got around the problem by reserving non-alpha characters @, <period>, <quote> for the deref returning loop. I don't know if it is my bug, or if there is a limit to your technique, but I suspect the problem is on my end.

There are probably other bugs, but I wanted to post this once it was basically working.

First is a batch file that does nothing but define a library of test macros. I use ${macroName} syntax to reference a macro within a macro. Once they are all defined I use a loop to find all the defined macros and call drefMacro macroName to de-reference or expand the macro definitions.

macroLib.bat:

Code: Select all

@echo off

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.StrLen=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 macro.1=do (%\n%
  setLocal enableDelayedExpansion %\n%
  echo in macro.1 prior to loop: a=%%a %\n%
  for /l %%a in (1,1,3) do echo inside inner macro.1 loop: a=%%a %\n%
  echo in macro.1 after loop: a=%%a %\n%
  set "part1=macro.1 Return Value" %\n%
  echo in macro.1: rtn=!part1! %\n%
  set "part2=" %\n%
  echo in macro.1 before "call" to macro.2: part2=!part2! %\n%
  %callMacro% ("part2") ${macro.2} %\n%
  echo in macro.1 after "call" to macro.2: part2=!part2! %\n%
  for /f "delims=" %%v in ("!part1!:!part2!") do endlocal^&set "%%~a=%%v" %\n%
)

set macro.2=do (%\n%
  setLocal enableDelayedExpansion %\n%
  echo in macro.2: a=%%a %\n%
  set "rtn=macro.2 Return Value" %\n%
  echo in macro.2 before call to macro.3: rtn=!rtn! %\n%
  %callMacro% ("rtn") ${macro.3} %\n%
  echo in macro.2 after call to macro.3: rtn=!rtn! %\n%
  for /f "delims=" %%v in ("!rtn!") do endlocal^&set "%%~a=%%v" %\n%
)

set macro.3=do (%\n%
  setLocal enableDelayedExpansion %\n%
  echo in macro.3: a=%%a %\n%
  set "rtn=!%%~a!:macro.3 Return Value" %\n%
  echo in macro.3 test ${falseMacroRef} %\n%
  echo in macro.3: rtn=!rtn! %\n%
  for /f "delims=" %%v in ("!rtn!") do endlocal^&set "%%~a=%%v" %\n%
)

echo ----- before drefMacro ----------
set macro
for /f "delims==" %%v in ('set macro. ^| findstr /r /i "/c:^macro."') do (
  if /i not "%%v"=="macro." call :derefMacro %%v
)
echo ----- after drefMacro ----------
set macro
echo:

exit /b

:derefMacro macroName
::
:: We don't know the name of any embedded macros so all local variables must
:: be named in such a way that they are "guaranteed" not to collide with the
:: macro names. I assume no macro name will ever start with "_temp". Of course
:: if we follow the convention that all macro names begin with "macro", then
:: this will never be a problem.
::
  setlocal enableDelayedExpansion
  set "_tempMacro=!%~1!"

  :derefMacro.Top
  set "_tempParse1=!_tempMacro!"

  :derefMacro.Parse
  set "_tempParse2=!_tempParse1:*${=!"
  if "!_tempParse2!"=="!_tempParse1!" goto :derefMacro.End
  if not defined _tempParse2 goto :derefMacro.End
  set "_tempParse3=!_tempParse2:*}=!"
  if "!_tempParse3!"=="!_tempParse2!" goto :derefMacro.End
  %callMacro% ("_tempMacro _tempLen1") %macro.StrLen%
  %callMacro% ("_tempParse2 _tempLen2") %macro.StrLen%
  %callMacro% ("_tempParse3 _tempSuffixLen") %macro.StrLen%
  set /a "_tempNameStart=_tempLen1-_tempLen2, _tempPrefixLen=_tempNameStart-2, _tempNameLen=_tempLen1-_tempPrefixLen-_tempSuffixLen-3"
  set "_tempName=!_tempMacro:~%_tempNameStart%,%_tempNameLen%!"
  if not defined %_tempName% (
    set "_tempParse1=!_tempParse2!"
    goto :derefMacro.Parse
  )
  set "_tempMacro=!_tempMacro:~0,%_tempPrefixLen%!!%_tempName%!!_tempMacro:~-%_tempSuffixLen%!"
  goto :derefMacro.Top

  :derefMacro.End
  set "_tempMacro=!_tempMacro:%%=%%~_!"
  set "_tempMacro=!_tempMacro:"=%%~.!"
  for %%l in ("!LF!") do set "_tempMacro=!_tempMacro:%%~l=%%~@!"
  for %%@ in ("!LF!") do for %%. in (^""") do for %%_ in ("%%") do (
    endlocal
    set "%~1=%_tempMacro%"
  )
exit /b


Next is a simple test program to excersize the macros. It simply loads the library and calls macro.1, which in turn "calls" macro.2 and macro.3. But actually the calls are embedded within the macro.1 definition by now.

It is Sooo much easier to include a library of macros than it is to include a library of functions :!: :D

simple macro test program:

Code: Select all

@echo off
setlocal

call macroLib.bat

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

exit /b


And here are the results:

Code: Select all

----- before drefMacro ----------
macro.1=do (
  setLocal enableDelayedExpansion
  echo in macro.1 prior to loop: a=%a
  for /l %a in (1,1,3) do echo inside inner macro.1 loop: a=%a
  echo in macro.1 after loop: a=%a
  set "part1=macro.1 Return Value"
  echo in macro.1: rtn=!part1!
  set "part2="
  echo in macro.1 before "call" to macro.2: part2=!part2!
  for /f "tokens=1-26" %a in ("part2") ${macro.2}
  echo in macro.1 after "call" to macro.2: part2=!part2!
  for /f "delims=" %v in ("!part1!:!part2!") do set "%~a=%v"
)
macro.2=do (
  setLocal enableDelayedExpansion
  echo in macro.2: a=%a
  set "rtn=macro.2 Return Value"
  echo in macro.2 before call to macro.3: rtn=!rtn!
  for /f "tokens=1-26" %a in ("rtn") ${macro.3}
  echo in macro.2 after call to macro.3: rtn=!rtn!
  for /f "delims=" %v in ("!rtn!") do set "%~a=%v"
)
macro.3=do (
  setLocal enableDelayedExpansion
  echo in macro.3: a=%a
  set "rtn=!%~a!:macro.3 Return Value"
  echo in macro.3 test ${falseMacroRef}
  echo in macro.3: rtn=!rtn!
  for /f "delims=" %v in ("!rtn!") do set "%~a=%v"
)
macro.StrLen=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
)
----- after drefMacro ----------
macro.1=do (
  setLocal enableDelayedExpansion
  echo in macro.1 prior to loop: a=%a
  for /l %a in (1,1,3) do echo inside inner macro.1 loop: a=%a
  echo in macro.1 after loop: a=%a
  set "part1=macro.1 Return Value"
  echo in macro.1: rtn=!part1!
  set "part2="
  echo in macro.1 before "call" to macro.2: part2=!part2!
  for /f "tokens=1-26" %a in ("part2") do (
  setLocal enableDelayedExpansion
  echo in macro.2: a=%a
  set "rtn=macro.2 Return Value"
  echo in macro.2 before call to macro.3: rtn=!rtn!
  for /f "tokens=1-26" %a in ("rtn") do (
  setLocal enableDelayedExpansion
  echo in macro.3: a=%a
  set "rtn=!%~a!:macro.3 Return Value"
  echo in macro.3 test ${falseMacroRef}
  echo in macro.3: rtn=!rtn!
  for /f "delims=" %v in ("!rtn!") do set "%~a=%v"
)
  echo in macro.2 after call to macro.3: rtn=!rtn!
  for /f "delims=" %v in ("!rtn!") do set "%~a=%v"
)
  echo in macro.1 after "call" to macro.2: part2=!part2!
  for /f "delims=" %v in ("!part1!:!part2!") do set "%~a=%v"
)
macro.2=do (
  setLocal enableDelayedExpansion
  echo in macro.2: a=%a
  set "rtn=macro.2 Return Value"
  echo in macro.2 before call to macro.3: rtn=!rtn!
  for /f "tokens=1-26" %a in ("rtn") do (
  setLocal enableDelayedExpansion
  echo in macro.3: a=%a
  set "rtn=!%~a!:macro.3 Return Value"
  echo in macro.3 test ${falseMacroRef}
  echo in macro.3: rtn=!rtn!
  for /f "delims=" %v in ("!rtn!") do set "%~a=%v"
)
  echo in macro.2 after call to macro.3: rtn=!rtn!
  for /f "delims=" %v in ("!rtn!") do set "%~a=%v"
)
macro.3=do (
  setLocal enableDelayedExpansion
  echo in macro.3: a=%a
  set "rtn=!%~a!:macro.3 Return Value"
  echo in macro.3 test ${falseMacroRef}
  echo in macro.3: rtn=!rtn!
  for /f "delims=" %v in ("!rtn!") do set "%~a=%v"
)
macro.StrLen=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
)

before call to macro.1: var=
in macro.1 prior to loop: a=var
inside inner macro.1 loop: a=1
inside inner macro.1 loop: a=2
inside inner macro.1 loop: a=3
in macro.1 after loop: a=var
in macro.1: rtn=macro.1 Return Value
in macro.1 before "call" to macro.2: part2=
in macro.2: a=part2
in macro.2 before call to macro.3: rtn=macro.2 Return Value
in macro.3: a=rtn
in macro.3 test ${falseMacroRef}
in macro.3: rtn=macro.2 Return Value:macro.3 Return Value
in macro.2 after call to macro.3: rtn=macro.2 Return Value:macro.3 Return Value
in macro.1 after "call" to macro.2: part2=macro.2 Return Value:macro.3 Return Value
after call to macro.1: var=macro.1 Return Value:macro.2 Return Value:macro.3 Return Value


Dave Benham
Last edited by dbenham on 21 May 2011 18:41, edited 2 times in total.

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

Re: Batch "macros" with arguments

#22 Post by Ed Dyreen » 20 May 2011 19:53

'
So you are reducing the overhead aswell! By derefferencing first, it won't have to be done at execution phase brilliant :D

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

Re: Batch "macros" with arguments

#23 Post by dbenham » 20 May 2011 21:19

I edited my prior post to fix a bug in the macroLib.bat code. I had forgotten the ENDLOCAL at the end of macro.1, macro.2 and macro.3. The test program was giving the correct visual output, but the local macro variables were still active after the "calls". All is good now (till the next bug).

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

Ed Dyreen wrote:So you are reducing the overhead aswell! By derefferencing first, it won't have to be done at execution phase brilliant

Thanks, but I'm not so sure it is brilliant, more like the only way that it can work. Even your examples were expanded during the definition phase when you used traditional delayed expansion. The big advantage of ${macroName} / derefMacro is we take control of when and how the macro is expanded, thus avoiding all of the problems associated with delayed expansion using !macroName!. Given that we can't predict how many layers deep the calls will go, trying to figure out how many times to escape the ^, ! and <LF> characters would be a nightmare!

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

Given that the macros enable delayed expansion within their body, we can't call them directly from the command prompt. They must be called from a batch file. But wouldn't it be nice to run them from the command prompt? I thought so, so I wrote:

callMacro.bat:

Code: Select all

@echo off
setlocal enableDelayedExpansion
set "macro.temp=!%~1!"
endlocal & for /f "tokens=2-27" %%a in ("%*") %macro.temp%
exit /b

This very small batch file is simply a batch wrapper around a macro. To call macro.StrLen you would use:

Code: Select all

callMacro macro.StrLen strVar [rtnVar]
or
call callMacro macro.StrLen strVar [rtnVar]

where [rtnVar] is the optional output variable.

We have reintroduced the call function overhead, but we are not likely to notice the impact during an interactive session!

Of course I am assuming the batch library (macroLib.bat in my test cases) has already been loaded (run) prior to calling callMacro.bat, and that callMacro.bat is in the current directory or the PATH.

CallMacro.bat can also be useful within a batch file when the definition of a macro or other compound statement is exceeding the limits of DOS. Using the macro directly expands the macro within the compound statement. Calling :callMacro takes the expansion out of the compound statement and puts it in the function. The only requirement is that callMacro.bat reside in the current directory or the PATH. Or you could copy the function into your batch file.

CallMacro.bat is not bullet proof - it does not gracefully handle arguments with special characters, especially the caret "^". Special handling could be added, but that will add extra overhead.

Dave Benham

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

Re: Batch "macros" with arguments

#24 Post by dbenham » 20 May 2011 22:30

OK - I need some advice on the best way to provide on-line help for macro libraries.

I see two obvious strategies:

1) Define the documentation as variables. Something like:

set macro.args.macroName= argument list only on one line
set macro.help.macroName= ${macro.doc.args.macroName} followed by multi-line description using %\n%

I really like this because the help is easily available using the SET command - No need to write a help function or macro. We can easily request help on a single command, or many commands. I also like the concept of keeping the help with the definitions - everything is in memory! But I'm worried about environment space bloat. Is there a practical limit to the size of the environment? I'm thinking the help text could end up being 1/4 to 1/2 the size of the macro definitions themselves.

2) Keep the documentation as text within the library batch file. Write a function to extract and display the desired help text via creative use of the FINDSTR command. I've seen many examples of this, and I've done this myself many times.

I would like to use option 1, but am thinking that option 2 is the safer, more conservative option.

What say the experts?

Dave Benham

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

Re: Batch "macros" with arguments

#25 Post by Ed Dyreen » 20 May 2011 23:46

'
I would implement both techniques, the help screen does not need to be fast in my opinion as you should not be in help screens most of the time. Variabes are still best if the messages are repetitive. strings like "a b c d ..." for mathematics. My script is horribly innefficient &a huge amount of variables are loaded. So I don't think the environment is gonna be a problem.

I have been using macros for a long time, Until now most output to con I handle with functions..
Some screens contain a lot of characters &the macros can only contain 4096 of them. I've read somewhere it's the maximum for any command.

I have done some progressing myself too, if it is of any interest to you, I have fallen back to an old habit delayed expansion :oops: I like your way of building the macro's. Can it work for my macros too or ? Is that not possible ?

I have a new problem now that i did not have before I used \n, I can no longer display the '!' anymore not even if i escape it 7 times ^^^^^^^!

Code: Select all

::--------------------------------------------------------------------------------------------------------------------------
:: Pre Define Create.Object >>
::(
   set ^"@Create.Object=( %\n2%^

      Setlocal EnableDelayedExpansion %\n2%^

      set "Name=%%~a" %\n2%^
      set "Extension=%%~b" %\n2%^

      set "StdIn=%%~c" %\n2%^
      set "StdOut=" %\n2%^
      set "SError=%%~d" %\n2%^

      if ^^^!Error^^^! equ 0 ( %\n2%^

         if /i ["^!Extension^!"] == ["File"] ( %\n2%^

            %forAmacro% ( '"^!Name^!"¦"^!StdIn^!"¦"%%~d"' ) do !@Create.File! %\n2%^
            %forRmacro% ( '"^!StdOut^!"' ) do set "StdOut="^^^!%%~r^^^!"¦" %\n2%^

            %forAmacro% ( '"^!Name^!"¦"^!StdIn^!"¦"%%~d"' ) do !@Create.FileName! %\n2%^
            %forRmacro% ( '"^!StdOut^!"' ) do set "StdOut=^!StdOut^!"^^^!%%~r^^^!"¦" %\n2%^

         ) else    if /i ["^!Extension^!"] == ["FileName"] ( %\n2%^

            %forAmacro% ( '"^!Name^!"¦"^!StdIn^!"¦"%%~d"' ) do !@Create.FileName! %\n2%^

         ) %\n2%^

      ) %\n2%^

      %forRmacro% ( %\n2%^

         '"^!Extension^!"¦^^^!StdOut^^^!"^!Error^!"' %\n2%^

      ) do ( %\n2%^

         Endlocal %\n2%^

         if /i ["%%~r"] == ["File"] ( %\n2%^

            set "%%~b.File=%%~s" %\n2%^
            set "%%~b.FileName=%%~t" %\n2%^
            set /a Error = %%~u %\n2%^

         ) else    if /i ["%%~r"] == ["FileName"] ( %\n2%^

            set "%%~b.FileName=%%~s" %\n2%^
            set /a Error = %%~t %\n2%^

         ) %\n2%^

      ) %\n2%^

   ) ^&( %\n2%^

      echo. %\n2%^
      echo. %\n2%^
         echo. @Create.Object: '%%~a.%%~b' %\n2%^
         echo.  StdOut.%%~b  : '^^^!%%~a.%%~b^^^!' %\n2%^

      if /i ["%%~b"] == ["File"] ( %\n2%^

         echo.  StdOut.FileName: '^^^!%%~a.FileName^^^!' %\n2%^

      ) %\n2%^

         echo.  Error        : '^^^!Error^^^!' %\n2%^

      !@See.Error! %\n2%^
   )"
::)
::
:: Post Define Create.Object <<
::--------------------------------------------------------------------------------------------------------------------------

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

Re: Batch "macros" with arguments

#26 Post by dbenham » 21 May 2011 17:52

Ed Dyreen wrote:I have done some progressing myself too, if it is of any interest to you, I have fallen back to an old habit delayed expansion. I like your way of building the macro's. Can it work for my macros too or ? Is that not possible ?

I'm not sure how to interpret your question. I am almost sure that there is no way to delay expansion of a macro within a macro until run time. It must be expanded during the definition phase. Even if it were possible to delay, I see no benefit to it.

In the examples I've seen you post you had delayed expansion enabled during the definition of the macro. Theoretically you should be able to embed a macro within a macro under these conditions with the correct escape sequences, but I wish you luck in maintaining such a code base. I know I never want to attempt that.

I strongly recommend that macros be defined with delayed expansion disabled, and embedded macros be expanded with the derefMacro function in a 2nd definition phase. I see no reason why this should not work for you. BTW, I'm not very happy with the name of derefMacro, and am open to suggestions.

I think we agree that all but the simplest macros will require delayed expansion at run time. My macros have SETLOCAL ENABLEDELAYEDEXPANSION within their definition - I do it because I want my macros to have their own local environment. But this is not necessary. You could opt to not enable delayed expansion within the body of the macro if you do something like the following:

pseudo-code

Code: Select all

:: delayed expansion should be disabled at this point, either explicitly, or implicitly
:: Define the macros

:: phase 1
set macro=....
:: phase 2
call drefMacro macro

:: End of macro definitions

setlocal enableDelayedExpansion
:: you can now call your macros that require delayed expansion

BUT, bear in mind that your macros will now be dependent on delayed expansion being set before the call.

----------------

I have discovered another use for macros - really just a twist. Up until now macros have had global scope. But they can be very useful when defined within a function.

In my original hexDump batch function I had a local auxiliary function called in multiple places within extensive loops. This really slowed the function down. I considered embedding the auxiliary function code within the loops, but was worried about readability and maintenance. I've since updated the function to use a locally defined macro and it provides a HUGE speed improvement.

Dave Benham

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

Re: Batch "macros" with arguments

#27 Post by Ed Dyreen » 21 May 2011 18:14

'
You said you had problems with nested macros and 'Dreference' function. that you could dref only twice . When I first define 4 macros and macro1 calls macro2 calls macro3 calls macro4, the code expands to :

Code: Select all

%forAmacro% ( *** ) do %forAmacro% ( *** ) do %forAmacro% ( *** ) do %forAmacro% ( *** ) do !@Macro!
Theoritically I can keep doing this forever, that is until the macro is full.
Can't you easily do this in disabled delayed expansion :?:

There is something I still can't do from inside a macro with enabled delayed expansion :

Code: Select all

set which=%%~a
%forAmacro% ( *** ) do call %%@^^^!which^^^!Macro%%
%someMacro% not a valid command

I've tried getting around it with

Code: Select all

%formacro% ( 'call %%@^^^!which^^^!Macro%%' ) do %%~^^^!     or     
%formacro% ( "%%@^^^!which^^^!Macro%%" ) do echo.%%~^^^!
I'm afraid it's just not going to happen :|
dbenham wrote:Given that we can't predict how many layers deep the calls will go, trying to figure out how many times to escape the ^, ! and <LF> characters would be a nightmare!
It doesn't matter how deep the macro is in enabled expansion .
^^^^^^^! to echo !
%%~^^^! to use token
%%~^! between quotes
or do you mean problems when dereferencing ? I don't understand .

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

Re: Batch "macros" with arguments

#28 Post by dbenham » 21 May 2011 18:54

I don't remember saying I had problems DREFing more than twice. I said I had successfully tested dereferencing macro1 that calls macro2 that calls macro3. The derefMacro function is designed to handle any number of deref operations, I just haven't tested past 2. (one call loops through and dereferences as much as necessary)

I tried that CALL macro syntax as well, and many others, all without success - hence my original macro calling macro question in the 1st post. Yes, the ${macro} / drefMacro combination is designed to resolve the issue.

Dave Benham

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

Re: Batch "macros" with arguments

#29 Post by jeb » 23 May 2011 13:35

Ed Dyreen wrote:Some screens contain a lot of characters &the macros can only contain 4096 of them. I've read somewhere it's the maximum for any


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

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

Re: Batch "macros" with arguments

#30 Post by Ed Dyreen » 10 Jun 2011 16:34

'
@for_entire_line works with the %%r token but I can't make it work with %%!

I know it has to do with the escapes that are changing behavior due to the !.
But I still can't solve it :(

Code: Select all

set $LF=^


:: Two empty lines are neccessary

set @MForEntireLine=for /f ^^^^^^^"eol^^^^=^^^^^^^%$LF%%$LF%^%$LF%%$LF%^^^%$LF%%$LF%^%$LF%%$LF%^^^^ delims^^^^=^^^^^^^"

set @MacroForEntireLine=%@MForEntireLine% %%^^^! in ( ";Please <<preserve this line>>" ) do echo %%^^^!

SetLocal EnableDelayedExpansion

%@MacroForEntireLine%
Last edited by Ed Dyreen on 15 Jun 2011 22:45, edited 1 time in total.

Post Reply