View unanswered posts | View active topics It is currently 21 Apr 2014 07:03



Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next
Macros with parameters appended 
Author Message
Expert

Joined: 30 Aug 2007 08:05
Posts: 658
Location: Germany
Post Macros with parameters appended
Hi all, (especially dbenham and tooComplex/Ed) :)

sometimes someone asks for a better solution for macros with parameters.

Currently the standard technic (from Ed) is to invoke a batch macro with parameters seems to be
Code:
%_forU% ( 'params' ) %@macro%

So you need a construct with two expansions, and the parameters are in the middle.

I would prefere something like (even I didn't use macros at all)
Code:
%myMacro% param1,param2
OR
%myMacro% (param1,param2)

How to solve this might not be obviously, but it can be done. :wink:

And the result isn't very complex, it's only a bit slower as it uses one extra FOR/L-Loop.
The main idea is this:
Code:
FOR/L %%n in (1,1,2) DO (
  if %%n==2 ( // Now we can access the parameters
     // get the parameters
     // run the macro
  )
) & set parameters=


And here is a working sample with strlen
Calling it via
%$strlen% resultVar,stringVar

Code:
@echo off
cls
setlocal DisableDelayedExpansion
set LF=^


::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

set argv=Empty
set $strLen=for /L %%n in (1 1 2) do ( %\n%
   if %%n==2 (%\n%
       setlocal enableDelayedExpansion%\n%
      for /F "tokens=1,2 delims=, " %%1 in ("!argv!") do (%\n%
         echo par1=%%1 %\n%
         echo par2=%%2 %\n%
         set "str=A!%%~2!"%\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 "%%~1=%%v") else echo %%v%\n%
      ) %\n%
   ) %\n%
) ^& set argv=,

set "testString=this has a length of 23"
%$strlen% result,testString
echo Length of testString ("%testString%") is %result%


jeb


21 Nov 2011 05:35
Profile
Expert

Joined: 12 Feb 2011 21:02
Posts: 1118
Location: United States (east coast)
Post Re: Macros with parameters appended
:shock: Awesome and Revolutionary :!:

Two minor inconveniences:

1) The parameter value of argV persists after the macro completes. I don't think there is anything we can do about that.

2) Any command added on the same line after the macro call will be executed twice

This code with your strlen macro
Code:
set "testString=this has a length of 23"
%$strlen% result,testString & echo Hello World %%n
echo Length of testString ("%testString%") is %result%
set argv

produces:
Code:
Hello world 1
par1=!argv!
par2=
Hello world 2
Length of testString ("this has a length of 23") is
argv=, result,testString

Great work jeb :!:
I think I will be adopting this macro style.


Dave Benham


21 Nov 2011 07:19
Profile
Expert

Joined: 16 May 2011 08:21
Posts: 1253
Location: Flanders_(Belgium)
Post Re: Macros with parameters appended
'
My god, this may prove to work pretty well actually ( you definately made dave happy ).

:x
How am I ever going to get my library stable if things keep changing at this rate ? impressive !
You know jeb, you are a freaking clever guy, luckily for clever people, there is always someone more clever than you.

And while you're at it, how about:
Code:
set /a $var=%StrLen% ( "I wish this worked" ) + %random%
Now that would be really impressive. :lol:

I have a feeling this topic is going to grow...


Last edited by Ed Dyreen on 21 Nov 2011 08:43, edited 6 times in total.



21 Nov 2011 07:42
Profile WWW
Expert

Joined: 30 Aug 2007 08:05
Posts: 658
Location: Germany
Post Re: Macros with parameters appended
dbenham wrote:
:shock: Awesome and Revolutionary :!:

Thanks :D


dbenham wrote:
Two minor inconveniences:

1) The parameter value of argV persists after the macro completes. I don't think there is anything we can do about that.

2) Any command added on the same line after the macro call will be executed twice


The solution for these two points is obviously :wink:

- Use setlocal direct before set argv
- Do the set argv and the rest only if %%n==1

Code:
@echo off
cls
setlocal DisableDelayedExpansion
set LF=^


::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

set argv=original
set $strLen=for /L %%n in (1 1 2) do if %%n==2 (%\n%
      for /F "tokens=1,2 delims=, " %%1 in ("!argv!") do (%\n%
         echo par1=%%1 %\n%
         echo par2=%%2 %\n%
         set "str=A!%%~2!"%\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 "%%~1=%%v") else echo %%v%\n%
      ) %\n%
) ELSE setlocal enableDelayedExpansion ^& set argv=,

set "testString=this has a length of 23"
%$strlen% result,testString & echo hello
set argv


Output
Quote:
hello
par1=result
par2=testString
Length of testString ("this has a length of 23") is 23
argv=original


jeb


21 Nov 2011 08:13
Profile
Expert

Joined: 12 Feb 2011 21:02
Posts: 1118
Location: United States (east coast)
Post Re: Macros with parameters appended
jeb wrote:
The solution for these two points is obviously :wink:

- Use setlocal direct before set argv
- Do the set argv and the rest only if %%n==1

Ding Ding Ding Ding Ding Ding Ding
WoooHooooo - Jackpot :!:

I am definitely adopting jeb's new macro style now :D

I will probably define a few helper macros:

macro_begin to encapsulate the 1st two lines

macro_end to define the final ELSE construct under normal circumstances

macro_endSafe to define the final ELSE construct with some additional stuff to prepare for the safe return technique

Thanks again jeb

Addendum - I just realized the solution is not quite perfect :|

Any command appended to the end of the line actually executes before the macro. This might throw off some people . . .
. . . but the technique is still awesome :D

Dave Benham


21 Nov 2011 09:09
Profile
Expert

Joined: 16 May 2011 08:21
Posts: 1253
Location: Flanders_(Belgium)
Post Re: Macros with parameters appended
'
dbenham wrote:
Any command appended to the end of the line actually executes before the macro. This might throw off some people . . .
but Dave, doesn't this solves that :?
Code:
set $strLen=for /L %%n in (1 1 2) do if %%n==2 (%\n%
      endlocal ^&cmd /c exit 0 %\n%
) ELSE setlocal enableDelayedExpansion ^& set argv=,
Code:
( %$strlen% result,testString ) &&echo.succes ||echo.failed
Ok guys, I'm in jeb rocks 8)


21 Nov 2011 09:31
Profile WWW
Expert

Joined: 12 Feb 2011 21:02
Posts: 1118
Location: United States (east coast)
Post Re: Macros with parameters appended
Ed Dyreen wrote:
dbenham wrote:
Any command appended to the end of the line actually executes before the macro. This might throw off some people . . .
but Dave, doesn't this solves that :?
Code:
set $strLen=for /L %%n in (1 1 2) do if %%n==2 (%\n%
      endlocal ^&cmd /c exit 0 %\n%
) ELSE setlocal enableDelayedExpansion ^& set argv=,

I don't understand your logic, or where you are going with this.

Please provide a complete but small working example that demonstrates that when executing
Code:
%strlen% "my string" & echo hello
ECHO HELLO executes after the strlen is computed. I don't see how this can be done.

Dave Benham


21 Nov 2011 10:01
Profile
Expert

Joined: 16 May 2011 08:21
Posts: 1253
Location: Flanders_(Belgium)
Post Re: Macros with parameters appended
'
Code:
@echo off

set $strLen=for /L %%n in (1 1 2) do if %%n==2 ( echo.instrlen ^!argv^! ^&endlocal ^&cmd /c exit 0 ) ELSE setlocal enableDelayedExpansion ^& set argv=,

( %$strlen% result,testString ) &&echo.succes ||echo.failed

pause
exit
Code:
instrlen , result,testString
succes
Druk op een toets om door te gaan. . .
You'll have to enclose in brackets :|


21 Nov 2011 10:10
Profile WWW
Expert

Joined: 12 Feb 2011 21:02
Posts: 1118
Location: United States (east coast)
Post Re: Macros with parameters appended
Of course - I see how it works now. Thanks.

But some one could still do it without brackets and be confused with the results. It's not a big deal, just something to look out for.

Dave Benham


21 Nov 2011 10:23
Profile
Expert

Joined: 22 Jan 2010 18:01
Posts: 1695
Location: Germany
Post Re: Macros with parameters appended
That's amazing!
I don't understand the behaviour that ^& set argv=, is able to catch the argument but, however, it works just fine.
That's indeed a trigger to commence working with macros. Thanks to each of you three.

Regards
aGerman (still impressed ...)


21 Nov 2011 16:03
Profile
Expert

Joined: 12 Feb 2011 21:02
Posts: 1118
Location: United States (east coast)
Post Re: Macros with parameters appended
aGerman wrote:
I don't understand the behaviour that ^& set argv=, is able to catch the argument

At first I was confused as well. But it is really quite simple, once you see it. It helps to see everything on one line:

Code:
set macro=for /l %%n in (1 1 2) do if %%n==2 (MACRO BUSINESS) else setlocal enableDelayedExpansion ^& set argv=,

%macro% arg1 arg2

:: the call expands to
:: for /l %%n in (1 1 2) do if %%n==2 (MACRO BUSINESS) else setlocal enableDelayedExpansion & set argv=, arg1 arg2

Really simple, yet clever :D


Dave Benham


21 Nov 2011 16:25
Profile
Expert

Joined: 22 Jan 2010 18:01
Posts: 1695
Location: Germany
Post Re: Macros with parameters appended
Now it dawns me :idea:
Thanks Dave :D

Regards
aGerman


21 Nov 2011 16:37
Profile
Expert

Joined: 06 Dec 2011 22:15
Posts: 621
Location: México City, México
Post Re: Macros with parameters appended
jeb: First of all, I want to offer you a VERY BIG CONGRATULATION :!: for all of your ideas, specially this one. :D :P

That said, this is a small observation:

WHY YOU DIDN'T EXPLAIN YOUR METHODS? I worked for over 25 years explaining computer themes to other people. I used to write larger and clearer explanations as the themes becomes harder. I hate the "use this as it is, but don't try to understand it" approach in new methods, because it prevents the new method be understood by other people and hence be modified and used for other purposes.

Below is my own explanation on this topic and a small additional contribution.

Suppose you have a macro-function that is usually executed this way:
Code:
for /F "tokens=1-26" %%a in ("param1 param2 resultvar") do %macroFunc%

How we may execute the macro in the following way?
Code:
%macroFuncWithParams% param1 param2 resultvar

Parameters placed after the macro needs to be taken and moved inside the FOR parentheses; this may be achieved by a two-step process:

1- Get the parameters
2- Execute the macro with them

That is, the macro must be executed two times: in the first step the parameters will be stored in an (argv) variable, and in the second step the macro will be really executed:
Code:
for %%s in (get_params exec_macro) do (
   if %%s == get_params (
      set argv=<rest of the line placed after the macro>
   ) else ( rem %%s == exec_macro
      for /F "tokens=1-26" %%a in (!argv!) do %macroFunc%
   )
)

However, the <rest of the line> is physically placed after the macro. This means that set argv= command must be the last part of the macro definition:
Code:
for %%s in (1 2) do (
   if %%s == 2 (
      for /f "tokens=1-26" %%a in (!argv!) do %macroFunc%
   ) else (
      set argv=<rest of the line...>
   )
)

There are still two parentheses after set argv=, but we can simply eliminate they because the FOR and ELSE part parentheses are placed here just for legibility:
Code:
for %%n in (1 2) do if %%n == 2 (
      for /f "tokens=1-26" %%a in (!argv!) do %macroFunc%
   ) else set argv=

This way, although the parts seems to be upside down, they execute in the right order. Note that we could place another command after ELSE separated by & and it also would belong to ELSE part, although there are not parentheses here. This point makes possible to include a SETLOCAL before the set argv= to avoid undesired modification of a variable with same name in the caller program, and to assure that delayed expansion is enabled when !argv! is processed:
Code:
for %%n in (1 2) do if %%n == 2 (
      for /f "tokens=1-26" %%a in (!argv!) do %macroFunc%
      endlocal
   ) else setlocal EnableDelayedExpansion & set argv=

However, because this method use textual substitution of whichever text placed after the macro, if another & COMMAND is placed after macro params, then that command also belongs to ELSE part and will also be executed before the macro, that is an unexpected behavior. The only way to avoid this effect is isolating the macro and parameters from the rest of the line by enclosing they in parentheses.



Second part: An additional contribution.

When we define a macroFuncWithParams variable with this format, we may duplicate the original macroFunc code in the macro execution part, taking the parameters from argv variable (as in jeb example above); or we may use the original macroFunc code over argv variable in a nested macro invocation:
Code:
for /F "tokens=1-26" %%a in ("!argv!") do %nestedMacroFunc%

This last case would be useful if we want to define a generic macro that could call any other macro-function in a standard way. For example, suppose now that we want to execute our original macro this way:
Code:
%let% resultVar=macroFunc(param1,param2)

How we can do that? Via a slightly different rearrangment of the macro parameters:
Code:
for %%n in (1 2) do if %%n == 2 (
      for /F "tokens=1-4 delims==(,) " %%1 in ("!argv!") do (
      set macroFunc=%%1
      set param1=%%2
      set param2=%%3
      set resultVar=%%4
      for /F "tokens=1-26" %%a in ("!param1! !param2! resultValue") do %%macroFunc%%
      for %%v in (!resultValue!) do endlocal & set "!resultVar!=%%v"
   ) else setlocal EnableDelayedExpansion & set argv=

We have, however, a big problem at this point. There is no way to directly execute a nested macro inside another one. We may left this problem apart for a while and test now that the method above is correct by executing a subroutine instead of a macro:
Code:
@echo off
cls
setlocal DisableDelayedExpansion
set LF=^



::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"


set strLen=(%\n%
   setlocal enableDelayedExpansion%\n%
   set "str=A!%%~b!"%\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 "%%~a" neq "" (set "%%~a=%%v") else echo %%v%\n%
)


set let=for %%n in (1 2) do if %%n==2 (%\n%
      for /F "tokens=1-4 delims==(,) " %%1 in ("!argv!") do (%\n%
         echo -------------------------- %\n%
         set macroFunc=%%1%\n%
         echo macroFunc=!macroFunc! %\n%
         set param1=%%2%\n%
         echo param1=!param1! %\n%
REM      set param2=%%3%\n%
REM      echo param2=!param2! %\n%
         set resultVar=%%3%\n%
         echo resultVar=!resultvar! %\n%
         call !macroFunc! !param1! !param2! resultValue%\n%
REM         for /F "tokens=1-26" %%a in ("!param1! !param2! resultValue") do (%\n%
REM             nested call %%%%macroFunc%%%% %\n%
REM         )%\n%
         for %%v in (!resultValue!) do endlocal^&set "%%1=%%v"%\n%
         echo -------------------------- %\n%
      )%\n%
   ) else setlocal enableDelayedExpansion ^& set argv=


set "testString=this has a length of 23"
echo Test string is ("%testString%")
echo/

echo Original (FOR /F ...) strLen macro invocation:
for /F "tokens=1-26" %%a in ("testString forResult") do %strLen%
echo Original FOR macro invocation result is %forResult%
echo/

echo Original "CALL :strLen testString subResult" subroutine invocation:
call :strLen testString subResult=
echo Original CALL subroutine invocation result is %subResult%
echo/

echo %%Let%% letResult=:strLen(testString) invocation:
%let% letResult=:strLen(testString)
echo LET letResult=strLen(testString) result is %letResult%

goto :EOF


:strLen string resultVar=
setlocal enableDelayedExpansion
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 "%~2" neq "" (set "%~2=%%v") else echo %%v
exit /B

The result:
Code:
Test string is ("this has a length of 23")

Original (FOR /F ...) strLen macro invocation:
Original FOR macro invocation result is 23

Original "CALL :strLen testString subResult" subroutine invocation:
Original CALL subroutine invocation result is 23

%Let% letResult=:strLen(testString) invocation:
--------------------------
macroFunc=:strLen
param1=testString
resultVar=letResult
--------------------------
LET letResult=strLen(testString) result is 23

In previous example we achieved to get a macro that allows to execute any function in a very standard way at expense of a somewhat slower execution because the additional CALL. But we will not stop at this point, right? So we try to solve now the nested macro problem. The only way to execute a nested macro is by embeding it inside the caller one so when the original macro is expanded for execution, it already contains the code of the nested one. This may be achieved by splitting the original macro in two parts: a Head, from the beginning up to the invocation of the nested macro, and a Tail, from the return of the nested invocation up to the end. This way, we can assemble the final macro by joining the Head, the nested macro, and the Tail this way:
Code:
set completeLet=!letHead!!macroFunc!!letTail!

After that, the execution of both the LET macro and the nested macro-function is immediate:
Code:
%completeLet%


EDIT: Continue at this matter.

However, there is no way to include/embed a macro into another one at execution time, so previous method imply to create a different macro for each combination of LET plus the particular macro-function, that is not the desired solution. This method may still be used to execute any subroutine-function in a clearer way and there is another instance where this solution may be useful: to get the value of an array element when the subscript vary inside a FOR loop (and it is not the FOR variable). This task may be achieved via a macro called ASET (array set) with this format: %aset% element=array[subscript]
Code:
set aset=for %%n in (1 2) do if %%n==2 (%\n%
      for /F "tokens=1-3 delims==[] " %%1 in ("!argv!") do (%\n%
         endlocal^&set "%%1=!%%2[%%3]!"%\n%
      )%\n%
   ) else setlocal enableDelayedExpansion^&set argv=

For example, in this SO question I posted this program segment:
Code:
set i=0
set j=1
for %%e in (%line%) do (
   set /A i+=1
   for %%j in (!j!) do set target=!target[%%j]!
   if !i! == !target! (
      for %%i in (!i!) do set heading=!heading[%%i]!
      echo !heading!%%~e
      set /A j+=1
   )
)

Using ASET macro, the segment may be rewritten in this clearer way:
Code:
set i=0
set j=1
for %%e in (%line%) do (
   set /A i+=1
   %aset% target=target[!j!]
   if !i! == !target! (
      %aset% heading=heading[!i!]
      echo !heading!%%~e
      set /A j+=1
   )
)

Antonio

PS - I modified previous description to include a missing detail that jeb noted in a posterior post.

Note: for %%n in (1 2) execute faster than for /L %%n in (1,1,2).

Note: In my opinion, there must be a standard in macro names that allows to know if a macro is used in the conventional for /F ... way instead of with parameters (to avoid confusions).

Note: I strongly encourage all of you, people, to adopt the standard of using %%1 %%2...%%9 to take macro parameters in macro definitions (and FOR /F "tokens=1-9 delims=,;= " %%1 invocation) instead of using %%a %%b...%%z (and FOR /F "tokens=1-26" %%a invocation) for several practical reasons, some of which are explained in my second post at
this topic.


Last edited by Aacini on 27 Dec 2011 00:43, edited 3 times in total.



12 Dec 2011 05:19
Profile
Expert

Joined: 30 Aug 2007 08:05
Posts: 658
Location: Germany
Post Re: Macros with parameters appended
Welcome Aacini to the expert discussions :)

Aacini wrote:
jeb: First of all, I want to offer you a VERY BIG CONGRATULATION :!: for all of your ideas, specially this one. :D :P
Thanks :D :)

Aacini wrote:
WHY YOU DIDN'T EXPLAIN YOUR METHODS?
They are obvious and self-explanatory :!:

Ok, perhaps not complete obvious :wink: , but I wrote this especially for Dave and Ed, as only expert should/could build these types of macros.

Aacini wrote:
I hate the "use this as it is, but don't try to understand it" approach in new methods, because it prevents the new method be understood by other people and hence be modified and used for other purposes.
I love good questions, and thanks for your explanation, so I don't have to do it.

Aacini wrote:
Because this trick use textual substitution of whichever text placed after the macro, if another & COMMAND is placed after macro params, that command will also be carried into the for body and hence also executed 2 times:
[...]
The only way to avoid this effect is isolating the macro and parameters from the rest of the line by enclosing they in parentheses, as Ed indicated.
Did I said that I love to disprove such declarations :?: 8)

I simply forgot to post the solution for this
Code:
set $SomeMacro=for /L %%n in (1 1 2) do if %%n==2 (%\n%
      rem *** MACRO Content
      for /f "delims=" %%r in ("!result!") do endlocal^& set "%%~3=%%~r" %\n%
   ) %\n%
) ELSE setlocal enableDelayedExpansion ^& echo ProofOfconcept is showed only once %%n ^& set argv=,

In this case it is really obvious, isn't it?

jeb


12 Dec 2011 06:26
Profile
Expert

Joined: 16 May 2011 08:21
Posts: 1253
Location: Flanders_(Belgium)
Post Re: Macros with parameters appended
'
Very obvious as usual :) , I see , :) .
Code:
@echo off

set $lf=^


::

set ^"\n=^^^%$lf%%$lf%^%$lf%%$lf%^^"

set $SomeMacro=for /L %%n in (1 1 2) do if %%n==2 (%\n%
      set argv%\n%
      for /f "delims=" %%r in ("!argv!") do endlocal^& echo.set "%%~3=%%~r" %\n%
   ) %\n%
) ELSE setlocal enableDelayedExpansion ^& echo ProofOfconcept is showed only once %%n ^& set argv=,


%$SomeMacro% yesyes

pause
exit
Code:
Omgevingsvariabele argv is niet gedefinieerd
set "%~3=!argv!"
Druk op een toets om door te gaan. . .


12 Dec 2011 07:37
Profile WWW
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next


Who is online

Users browsing this forum: Bing [Bot], Google [Bot] and 14 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group.
Forum style by Vjacheslav Trushkin for Free Forums/DivisionCore.