Automating the generation of macros usable within other macros

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
jfl
Posts: 226
Joined: 26 Oct 2012 06:40
Location: Saint Hilaire du Touvet, France
Contact:

Automating the generation of macros usable within other macros

#1 Post by jfl » 16 Feb 2024 09:51

I'm starting a new thread, because the discussion in Macro for Dummies is diverging way beyond the original subject.

I've extended the four macros shown in post #15 in the above thread, to define four additional %%variables after 1 to 4 %expansions%:
  • %%^> will generate a '>' character
  • %%^< will generate a '<' character
  • %%^& will generate a '&' character
  • %%^| will generate a '|' character
Together with the previously defined %%! and %%^^ variables, they allow to easily generate multiple versions of the same macro, respectively for direct use, or inclusion with 1, 2, or 3 levels of other macros.
Here are the extended FOR!, FOR!2, FOR!3 and FOR!4 macros:

Code: Select all

:# Define a %FOR!% macro, itself defining %%^^=^, %%!=!, %%^>=>, %%^<=<, %%^&=&, %%^|=| after one %expansion%
for %%p in (%%) do for /f %%! in ("! =! ^^^!") do for /f %%1 in ("^ ^^^^ !!") do ^
for %%~ in ("") do for %%+ in (%%1%%~~) do for /f "tokens=2" %%- in ("!=! %%1 %%+%%+") do ^
set  FOR%%!=for /f %%%%! in ^(^"%%! =%%! %%+%%+%%+%%!^"^) do for /f "tokens=2" %%%%1%%1 in ^(^"%%!=%%! %%1 . %%1%%1%%1%%1%%1%%!=%%1%%!^"^) do^
 for %%p%%1^> in (%%-^>) do for %%p%%1^< in (%%-^<) do for %%p%%1^& in (%%+^&) do for %%p%%1^| in (%%-^|) do

:# Define a %FOR!2% macro, itself defining %%^^=^, %%!=!, %%^>=>, %%^<=<, %%^&=&, %%^|=| after two %expansions%
for %%p in (%%) do for /f %%! in ("! =! ^^^!") do for /f %%1 in ("^ ^^^^ !!") do for %%2 in (%%1%%1%%1%%1) do ^
for %%~ in (%%1) do for %%+ in (%%2%%~~) do for /f "tokens=2" %%- in ("!=! %%1%%1%%1 %%+%%+") do ^
set FOR%%!2=for /f %%%%! in ^(^"%%! =%%! %%+%%+%%+%%!^"^) do for /f "tokens=2" %%%%1%%1 in ^(^"%%!=%%! %%1%%1 %%2%%2%%2%%2%%~~%%~~%%~~%%!=%%~~%%~~%%~~%%!^"^) do^
 for %%p%%1^> in (%%-^>) do for %%p%%1^< in (%%-^<) do for %%p%%1^& in (%%-^&) do for %%p%%1^| in (%%-^|) do
 
:# Define a %FOR!3% macro, itself defining %%^^=^, %%!=!, %%^>=>, %%^<=<, %%^&=&, %%^|=| after three %expansions%
for %%p in (%%) do for /f %%! in ("! =! ^^^!") do for /f %%1 in ("^ ^^^^ !!") do for %%2 in (%%1%%1%%1%%1) do for %%3 in (%%2%%2%%2%%2) do ^
for %%~ in (%%2%%1) do for %%+ in (%%3%%~~) do for /f "tokens=2" %%- in ("!=! %%2%%1%%1%%1 %%+%%+") do ^
set FOR%%!3=for /f %%%%! in ^(^"%%! =%%! %%+%%+%%+%%!^"^) do for /f "tokens=2" %%%%1%%1 in ^(^"%%!=%%! %%2 %%3%%3%%3%%3%%~~%%~~%%~~%%!=%%~~%%~~%%~~%%!^"^) do^
 for %%p%%1^> in (%%-^>) do for %%p%%1^< in (%%-^<) do for %%p%%1^& in (%%-^&) do for %%p%%1^| in (%%-^|) do

:# Define a %FOR!4% macro, itself defining %%^^=^, %%!=!, %%^>=>, %%^<=<, %%^&=&, %%^|=| after four %expansions%
for %%p in (%%) do for /f %%! in ("! =! ^^^!") do for /f %%1 in ("^ ^^^^ !!") do for %%2 in (%%1%%1%%1%%1) do for %%3 in (%%2%%2%%2%%2) do for %%4 in (%%3%%3%%3%%3) do ^
for %%~ in (%%3%%2%%1) do for %%+ in (%%4%%~~) do for /f "tokens=2" %%- in ("!=! %%2%%2%%2%%1%%1%%1 %%+%%+") do ^
set FOR%%!4=for /f %%%%! in ^(^"%%! =%%! %%+%%+%%+%%!^"^) do for /f "tokens=2" %%%%1%%1 in ^(^"%%!=%%! %%2%%2 %%4%%4%%4%%4%%~~%%~~%%~~%%!=%%~~%%~~%%~~%%!^"^) do^
 for %%p%%1^> in (%%-^>) do for %%p%%1^< in (%%-^<) do for %%p%%1^& in (%%-^&) do for %%p%%1^| in (%%-^|) do
Example of use:

Code: Select all

@echo off
goto :dostips1.main

:dostips1.test
:# Two versions of the same ABORT macro. The macro argument is an error message to display on stderr before exiting.
%FOR!%  set  ABORT=for %%# in (1 2) do if %%#==2 ^(echo Error.%%!ERRMSG%%! Aborting.%%^>%%^&2 %%^& exit /b 1^) else setlocal EnableDelayedExpansion %%^& set ERRMSG=
%FOR!2% set ABORT2=for %%# in (1 2) do if %%#==2 ^(echo Error.%%!ERRMSG%%! Aborting.%%^>%%^&2 %%^& exit /b 1^) else setlocal EnableDelayedExpansion %%^& set ERRMSG=

(set \n=^^^
%# This creates an escaped Line Feed for use in multiline macros - DO NOT ALTER #%
)

:# Test the ABORT macro within another macro.
:# The EDE_TEST macro tests a trick issue about delayed expansion.
%FOR!% set EDE_TEST=(%\n%
  if not "%%!%%!"=="" %ABORT2% Must be used with enabled delayed expansion.%\n:# Check the EDE prerequisite =%
  for /f "tokens=2" %%t in ("%%!%%! one two three four") do echo Test 1 token 2 = %%t %\n%
  for /f "tokens=2" %%t in ("%%!%%! one two%%!%%! three four") do echo Test 2 token 2 = %%t %\n%
  for /f "tokens=2" %%t in ("%%!%%! one two%%!=%%! three four") do echo Test 3 token 2 = %%t %\n%
  for /f "tokens=2" %%t in ("%%!=%%! one two%%!%%! three four") do echo Test 4 token 2 = %%t %\n%
  for /f "tokens=2" %%t in ("%%!=%%! one two%%!=%%! three four") do echo Test 5 token 2 = %%t %\n%
)
set EDE_TEST
%EDE_TEST%
exit /b

:dostips1.init

:# Define a %FOR!% macro, itself defining %%^^=^, %%!=!, %%^>=>, %%^<=<, %%^&=&, %%^|=| after one %expansion%
for %%p in (%%) do for /f %%! in ("! =! ^^^!") do for /f %%1 in ("^ ^^^^ !!") do ^
for %%~ in ("") do for %%+ in (%%1%%~~) do for /f "tokens=2" %%- in ("!=! %%1 %%+%%+") do ^
set  FOR%%!=for /f %%%%! in ^(^"%%! =%%! %%+%%+%%+%%!^"^) do for /f "tokens=2" %%%%1%%1 in ^(^"%%!=%%! %%1 . %%1%%1%%1%%1%%1%%!=%%1%%!^"^) do^
 for %%p%%1^> in (%%-^>) do for %%p%%1^< in (%%-^<) do for %%p%%1^& in (%%+^&) do for %%p%%1^| in (%%-^|) do

:# Define a %FOR!2% macro, itself defining %%^^=^, %%!=!, %%^>=>, %%^<=<, %%^&=&, %%^|=| after two %expansions%
for %%p in (%%) do for /f %%! in ("! =! ^^^!") do for /f %%1 in ("^ ^^^^ !!") do for %%2 in (%%1%%1%%1%%1) do ^
for %%~ in (%%1) do for %%+ in (%%2%%~~) do for /f "tokens=2" %%- in ("!=! %%1%%1%%1 %%+%%+") do ^
set FOR%%!2=for /f %%%%! in ^(^"%%! =%%! %%+%%+%%+%%!^"^) do for /f "tokens=2" %%%%1%%1 in ^(^"%%!=%%! %%1%%1 %%2%%2%%2%%2%%~~%%~~%%~~%%!=%%~~%%~~%%~~%%!^"^) do^
 for %%p%%1^> in (%%-^>) do for %%p%%1^< in (%%-^<) do for %%p%%1^& in (%%-^&) do for %%p%%1^| in (%%-^|) do
 
:# Define a %FOR!3% macro, itself defining %%^^=^, %%!=!, %%^>=>, %%^<=<, %%^&=&, %%^|=| after three %expansions%
for %%p in (%%) do for /f %%! in ("! =! ^^^!") do for /f %%1 in ("^ ^^^^ !!") do for %%2 in (%%1%%1%%1%%1) do for %%3 in (%%2%%2%%2%%2) do ^
for %%~ in (%%2%%1) do for %%+ in (%%3%%~~) do for /f "tokens=2" %%- in ("!=! %%2%%1%%1%%1 %%+%%+") do ^
set FOR%%!3=for /f %%%%! in ^(^"%%! =%%! %%+%%+%%+%%!^"^) do for /f "tokens=2" %%%%1%%1 in ^(^"%%!=%%! %%2 %%3%%3%%3%%3%%~~%%~~%%~~%%!=%%~~%%~~%%~~%%!^"^) do^
 for %%p%%1^> in (%%-^>) do for %%p%%1^< in (%%-^<) do for %%p%%1^& in (%%-^&) do for %%p%%1^| in (%%-^|) do

:# Define a %FOR!4% macro, itself defining %%^^=^, %%!=!, %%^>=>, %%^<=<, %%^&=&, %%^|=| after four %expansions%
for %%p in (%%) do for /f %%! in ("! =! ^^^!") do for /f %%1 in ("^ ^^^^ !!") do for %%2 in (%%1%%1%%1%%1) do for %%3 in (%%2%%2%%2%%2) do for %%4 in (%%3%%3%%3%%3) do ^
for %%~ in (%%3%%2%%1) do for %%+ in (%%4%%~~) do for /f "tokens=2" %%- in ("!=! %%2%%2%%2%%1%%1%%1 %%+%%+") do ^
set FOR%%!4=for /f %%%%! in ^(^"%%! =%%! %%+%%+%%+%%!^"^) do for /f "tokens=2" %%%%1%%1 in ^(^"%%!=%%! %%2%%2 %%4%%4%%4%%4%%~~%%~~%%~~%%!=%%~~%%~~%%~~%%!^"^) do^
 for %%p%%1^> in (%%-^>) do for %%p%%1^< in (%%-^<) do for %%p%%1^& in (%%-^&) do for %%p%%1^| in (%%-^|) do

exit /b

:dostips1.main
for %%x in (Disable Enable) do (
  echo.
  setlocal %%xDelayedExpansion
  for /f "tokens=2" %%e in ("!! Dis En") do echo Expansion is %%eabled
  call :dostips1.init
  call :dostips1.test
  endlocal
)
exit /b
You'll notice how the ABORT and ABORT2 macros are generated with identical source code. Yet ABORT2 is usable within another macro, whereas ABORT is only useable directly.
This common source code is also relatively straightforward, despite all the tricky characters (! > &) involved.

The test runs twice, with delayed expansion enabled then disabled, and its output is:

Code: Select all

Expansion is Disabled
EDE_TEST=(
  if not "!!"=="" for %# in (1 2) do if %#==2 (echo Error.!ERRMSG! Aborting.>&2 & exit /b 1) else setlocal EnableDelayedExpansion & set ERRMSG= Must be used with enabled delayed expansion.
  for /f "tokens=2" %t in ("!! one two three four") do echo Test 1 token 2 = %t
  for /f "tokens=2" %t in ("!! one two!! three four") do echo Test 2 token 2 = %t
  for /f "tokens=2" %t in ("!! one two!=! three four") do echo Test 3 token 2 = %t
  for /f "tokens=2" %t in ("!=! one two!! three four") do echo Test 4 token 2 = %t
  for /f "tokens=2" %t in ("!=! one two!=! three four") do echo Test 5 token 2 = %t
)
Error. Must be used with enabled delayed expansion. Aborting.

Expansion is Enabled
EDE_TEST=(
  if not "!!"=="" for %# in (1 2) do if %#==2 (echo Error.!ERRMSG! Aborting.>&2 & exit /b 1) else setlocal EnableDelayedExpansion & set ERRMSG= Must be used with enabled delayed expansion.
  for /f "tokens=2" %t in ("!! one two three four") do echo Test 1 token 2 = %t
  for /f "tokens=2" %t in ("!! one two!! three four") do echo Test 2 token 2 = %t
  for /f "tokens=2" %t in ("!! one two!=! three four") do echo Test 3 token 2 = %t
  for /f "tokens=2" %t in ("!=! one two!! three four") do echo Test 4 token 2 = %t
  for /f "tokens=2" %t in ("!=! one two!=! three four") do echo Test 5 token 2 = %t
)
Test 1 token 2 = two
Test 2 token 2 = four
Test 3 token 2 = three
Test 4 token 2 = two
Test 5 token 2 = two
Notice how the EDE_TEST macro has exactly the same content in the two cases, whereas it was generated with delayed expansion disabled in the first case, and enabled in the second.

In the first case, running with delayed expansion disabled, the ABORT2 macro fires and outputs the abort message as intended before exiting.

In the second case, the test shows the issue that I reported in Macros for Dummies post #14.
This validates the workaround I implemented, but I still don't understand why things work this way.


Anyway, I'm well aware that these four FOR!* macros aren't general enough to cover all macro inclusion cases.
For example they will generate too many carets inside quoted strings. (Workaround in simple cases: Define quoted strings with ^" at both ends.)

The only bullet-proof solution would be to write a batch parser in batch, and use it to automatically create new macros based on original ones, adding the right number of ^ based on the parser state.
Maybe writing such a parser is easier to do now, with the efficient macros for looping, and breaking out of loops, that we have now!

In the short term, any idea for improving that set of macros for generating other macros, or propose alternatives, is welcome. :)

One side issue I'm still investigating is a good way to define a %%variable that generates a %.
Using a %%p variable is easy, but this might interfere with a user-defined %%p variable.
I tried using the same syntax with a caret, as for the other tricky characters: for %%^% in (1 2) do ...
This does loop twice as intended, proving that % itself can be used as a %%variable.
But I've not managed to use the %%^% value as expected.

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

Re: Automating the generation of macros usable within other macros

#2 Post by jeb » 18 Feb 2024 03:06

Hi jfl,
jfl wrote:
16 Feb 2024 09:51
One side issue I'm still investigating is a good way to define a %%variable that generates a %.
Using a %%p variable is easy, but this might interfere with a user-defined %%p variable.
I tried using the same syntax with a caret, as for the other tricky characters: for %%^% in (1 2) do ...
This does loop twice as intended, proving that % itself can be used as a %%variable.
But I've not managed to use the %%^% value as expected.
The FOR %%^% can't work, it should be FOR %%%%
To access the %-metavar you have to use the tilde %%~%%
Without tilde "Text%%%%1234" the expression is handled as
"Text%%_%%1_234"
->
"Text%_<expansion of %%1 or stay when %%1 does not exist>234"

Code: Select all

for %%%% in (%%) do echo A single percent "%%~%%"

jfl
Posts: 226
Joined: 26 Oct 2012 06:40
Location: Saint Hilaire du Touvet, France
Contact:

Re: Automating the generation of macros usable within other macros

#3 Post by jfl » 18 Feb 2024 08:48

jeb wrote:
18 Feb 2024 03:06
To access the %-metavar you have to use the tilde %%~%%
Yes, thanks, I found that yesterday too, but could not post an update as the Dostips server was unresponsive.
Obviously the ~ forces the parser to skip phase 1.1, and enter phase 1.2 directly.
Here's an updated set of macros integrating this. I've renamed FOR! as FOR!1, as this simplifies the alignment of my test code.

Code: Select all

:# Define a %FOR!1% macro, itself defining %%!=!, %%^^=^, %%~%%=%, %%^>=>, %%^<=<, %%^&=&, %%^|=| after one %expansion%
for /f %%! in ("! =! ^^^!") do for /f %%1 in ("^ ^^^^ !!") do ^
for %%%% in (%%) do for %%~ in ("") do for %%+ in (%%1%%~~) do for /f "tokens=2" %%- in ("!=! %%1 %%+%%+") do ^
set  FOR%%!1=for /f %%%%! in ^(^"%%! =%%! %%+%%+%%+%%!^"^) do for /f "tokens=2" %%%%1%%1 in ^(^"%%!=%%! %%1 . %%1%%1%%1%%1%%1%%!=%%1%%!^"^) do^
 for %%%% in (%%) do for %%~%%%%1^> in (%%-^>) do for %%~%%%%1^< in (%%-^<) do for %%~%%%%1^& in (%%+^&) do for %%~%%%%1^| in (%%-^|) do

:# Define a %FOR!2% macro, itself defining %%!=!, %%^^=^, %%~%%=%, %%^>=>, %%^<=<, %%^&=&, %%^|=| after two %expansions%
for /f %%! in ("! =! ^^^!") do for /f %%1 in ("^ ^^^^ !!") do for %%2 in (%%1%%1%%1%%1) do ^
for %%%% in (%%) do for %%~ in (%%1) do for %%+ in (%%2%%~~) do for /f "tokens=2" %%- in ("!=! %%1%%1%%1 %%+%%+") do ^
set FOR%%!2=for /f %%%%! in ^(^"%%! =%%! %%+%%+%%+%%!^"^) do for /f "tokens=2" %%%%1%%1 in ^(^"%%!=%%! %%1%%1 %%2%%2%%2%%2%%~~%%~~%%~~%%!=%%~~%%~~%%~~%%!^"^) do^
 for %%%% in (%%) do for %%~%%%%1^> in (%%-^>) do for %%~%%%%1^< in (%%-^<) do for %%~%%%%1^& in (%%-^&) do for %%~%%%%1^| in (%%-^|) do
 
:# Define a %FOR!3% macro, itself defining %%!=!, %%^^=^, %%~%%=%, %%^>=>, %%^<=<, %%^&=&, %%^|=| after three %expansions%
for /f %%! in ("! =! ^^^!") do for /f %%1 in ("^ ^^^^ !!") do for %%2 in (%%1%%1%%1%%1) do for %%3 in (%%2%%2%%2%%2) do ^
for %%%% in (%%) do for %%~ in (%%2%%1) do for %%+ in (%%3%%~~) do for /f "tokens=2" %%- in ("!=! %%2%%1%%1%%1 %%+%%+") do ^
set FOR%%!3=for /f %%%%! in ^(^"%%! =%%! %%+%%+%%+%%!^"^) do for /f "tokens=2" %%%%1%%1 in ^(^"%%!=%%! %%2 %%3%%3%%3%%3%%~~%%~~%%~~%%!=%%~~%%~~%%~~%%!^"^) do^
 for %%%% in (%%) do for %%~%%%%1^> in (%%-^>) do for %%~%%%%1^< in (%%-^<) do for %%~%%%%1^& in (%%-^&) do for %%~%%%%1^| in (%%-^|) do

:# Define a %FOR!4% macro, itself defining %%!=!, %%^^=^, %%~%%=%, %%^>=>, %%^<=<, %%^&=&, %%^|=| after four %expansions%
for /f %%! in ("! =! ^^^!") do for /f %%1 in ("^ ^^^^ !!") do for %%2 in (%%1%%1%%1%%1) do for %%3 in (%%2%%2%%2%%2) do for %%4 in (%%3%%3%%3%%3) do ^
for %%%% in (%%) do for %%~ in (%%3%%2%%1) do for %%+ in (%%4%%~~) do for /f "tokens=2" %%- in ("!=! %%2%%2%%2%%1%%1%%1 %%+%%+") do ^
set FOR%%!4=for /f %%%%! in ^(^"%%! =%%! %%+%%+%%+%%!^"^) do for /f "tokens=2" %%%%1%%1 in ^(^"%%!=%%! %%2%%2 %%4%%4%%4%%4%%~~%%~~%%~~%%!=%%~~%%~~%%~~%%!^"^) do^
 for %%%% in (%%) do for %%~%%%%1^> in (%%-^>) do for %%~%%%%1^< in (%%-^<) do for %%~%%%%1^& in (%%-^&) do for %%~%%%%1^| in (%%-^|) do
There are common parts in each macro, so it's possible to factor them out, and simplify this further.

jfl
Posts: 226
Joined: 26 Oct 2012 06:40
Location: Saint Hilaire du Touvet, France
Contact:

Re: Automating the generation of macros usable within other macros

#4 Post by jfl » 25 Feb 2024 11:56

I went way too fast: There were two serious problems with the %%>, %%<, %%&, %%| definitions added in the previous set of macros!
  1. The versions generated with delayed expansion on and off were not identical.
    They worked when used in the same expansion mode they were defined in, but not when used in the opposite mode.
    (Earlier versions were identical. I inadvertently broke that when I added these new definitions.)
  2. The definitions added for these %%>, %%<, %%&, %%| variables did not expand correctly with delayed expansion on, in the absence of a ! character .
The fix for the second problem required prepending a !=! sequence to each of these definitions, like for the %%^ definition.
The fix for the first problem required changing each definition to a conditional definition, again like for the %%^ definition.

I implemented the fix... It worked fine... But I won't post these macros here, as they had become horrible monsters!
The FOR!1 to FOR!4 macros values lengths were respectively 172, 383, 801, 2453 bytes!
This used a non-negligible portion of the environment variables space!

To address that issue, I decided to redesign everything from scratch.
  • The old version used %%N (with N=1 to 5) %%variables containing 4^(N-1) carets.
    This was useful to represent any large number of carets as a shorter sequence of %%N variables, as if the number were written in base 4.
    But experience had proved that all the long strings of carets I needed were either of the form 2^N, or (mostly) (2^N)-1.
    And the latter required lots of "base 4" digits.
    The new version uses %%N (with N=1 to 8​) %%variables containing (2^N)-1 carets.
    This had no effect on the macros sizes, but made all carets sequences definitions simpler, being either %%N, or %%N%%1 for 2^N carets.
  • The %%%% definition was useful for generating a % followed by another character, _without_ this expanding as a %%variable.
    For example: %%~%%2 -> %2, and _not_ %%2 -> ^^^
    But that %%~%%x syntax was definitely not intuitive to understand when reading the code.
    I changed that to a %%: %%variable. For example: %%:2 -> %2
    Again, this makes the source shorter and simpler to read, but has little effect on the final sizes.
  • The four %%>, %%<, %%&, %%| definitions needed the same number of protective carets in each expansion modes.
    This could be factored-out by defining this in a intermediate %%variable.
    Choosing %%| (The last %%variable defined) for that intermediate variable avoided using any additional %%variable.
    This had a significant effect on the macro sizes, with FOR!4 decreasing from 2453 to 1390 bytes!
  • The two largest %%N variables used in each macro were %%2 and %%4 for FOR!2; %%4 and %%6 for FOR!3; and %%6 and %%8 for FOR!4.
    Instead of expanding them in the FOR!N definition, I defined two more intermediate variables %%< and %%> defining these sequences of carets at run time.
    This had an even more significant effect on the macro sizes, with the 4 sizes now down to 155, 269, 317, 358 bytes respectively.
  • FOR!1 and FOR!2 are further optimized by removing some intermediate variables, which have no or even negative effects on them.
Here's that last version:

Code: Select all

:# The FOR!1 to FOR!4 macros below can be defined and used in any delayed expansion mode.
:# %%N variables (with N=1 to 8) contain sequences of (2^N)-1 carets. Ex: %%1=^, %%2=^^^, %%3=^^^^^^^, etc.
:# %%: generates a %. Using it in a value allows generating another %%variable, expanded when the value is used again. Ex: %%:2 -> %2 -> ^^^.
:# %# Inline comments #% below write !!v instead of %%v. Else %%v expands, and often fails with a syntax error, despite being in a comment.

:# Define a %FOR!1% macro, itself defining %%:=%, %%!=!, %%^^=^, %%^>=>, %%^<=<, %%^&=&, %%^|=| after one %expansion%
for %%: in (%%) do for /f %%! in ("! =! ^^^!") do for /f %%1 in ("^ ^^^^ !!") do for %%2 in (%%1%%1%%1) do ^
set  FOR%%!1=for %%:: in (%%) do for /f %%%%! in ^("%%! =%%! %%2%%!"^) do for /f %%%%1%%1 in ^("%%1 %%1%%!=%%1%%!%%2%%1"^) do^
 for %%%%1^> in ^(%%1^>^) do for %%%%1^< in ^(%%1^<^) do for %%%%1^& in ^(%%1^&^) do for %%%%1^| in ^(%%1^|^) do

:# Define a %FOR!2% macro, itself defining %%:=%, %%!=!, %%^^=^, %%^>=>, %%^<=<, %%^&=&, %%^|=| after two %expansions%
for %%: in (%%) do for /f %%! in ("! =! ^^^!") do for /f %%1 in ("^ ^^^^ !!") do for %%2 in (%%1%%1%%1) do ^
for %%3 in (%%2%%2%%1) do for %%4 in (%%3%%3%%1) do ^
set FOR%%!2=for /f %%%%1^> in ^("%%4") do%# Temporarily store in !!> the largest caret set used below #%^
 for %%:: in (%%) do for /f %%%%! in ^("%%! =%%! %%:>%%!"^) do for /f "tokens=2" %%%%1%%1 in ^("%%!=%%! %%1%%1 %%2%%!=%%2%%!%%:>%%1"^) do^
 for /f %%%%1^| in ^("%%1 %%2%%!=%%2%%!%%:>"^) do%# Factor-out in !!| the common caret protection for > < & ! definitions #%^
 for /f %%%%1^> in ^("%%:|>"^) do for /f %%%%1^< in ^("%%:|<"^) do for /f %%%%1^& in ^("%%:|&"^) do for /f %%%%1^| in ^("%%:||"^) do

:# Define a %FOR!3% macro, itself defining %%:=%, %%!=!, %%^^=^, %%^>=>, %%^<=<, %%^&=&, %%^|=| after three %expansions%
for %%: in (%%) do for /f %%! in ("! =! ^^^!") do for /f %%1 in ("^ ^^^^ !!") do for %%2 in (%%1%%1%%1) do ^
for %%3 in (%%2%%2%%1) do for %%4 in (%%3%%3%%1) do %# This macro also needs !!5 and !!6, but they're defined indirectly in !!< and !!> below #%^
set FOR%%!3=for /f %%%%1^< in ^("%%4") do for /f %%%%1^> in ^("%%:<%%:<%%:<%%:<%%1%%1%%1") do%# Temporarily define !!<=!!4 and !!>=!!6 #%^
 for %%:: in (%%) do for /f %%%%! in ^("%%! =%%! %%:>%%!"^) do for /f "tokens=2" %%%%1%%1 in ^("%%!=%%! %%2%%1 %%:<%%!=%%:<%%!%%:>%%1"^) do^
 for /f "tokens=2" %%%%1^| in ^("%%!=%%! %%2 %%:<%%!=%%:<%%!%%:>"^) do%# Factor-out in !!| the common caret protection for > < & ! definitions #%^
 for /f %%%%1^> in ^("%%:|>"^) do for /f %%%%1^< in ^("%%:|<"^) do for /f %%%%1^& in ^("%%:|&"^) do for /f %%%%1^| in ^("%%:||"^) do

:# Define a %FOR!4% macro, itself defining %%:=%, %%!=!, %%^^=^, %%^>=>, %%^<=<, %%^&=&, %%^|=| after four %expansions%
for %%: in (%%) do for /f %%! in ("! =! ^^^!") do for /f %%1 in ("^ ^^^^ !!") do for %%2 in (%%1%%1%%1) do ^
for %%3 in (%%2%%2%%1) do for %%4 in (%%3%%3%%1) do %# This macro also needs !!5 to !!8, but they're defined indirectly in !!<=!!6 and !!>=!!8 below #%^
set FOR%%!4=for /f %%%%1^< in ^("%%4") do for /f %%%%1^< in ^("%%:<%%:<%%:<%%:<%%1%%1%%1") do for /f %%%%1^> in ^("%%:<%%:<%%:<%%:<%%1%%1%%1") do^
 for %%:: in (%%) do for /f %%%%! in ^("%%! =%%! %%:>%%!"^) do for /f "tokens=2" %%%%1%%1 in ^("%%!=%%! %%3%%1 %%:<%%!=%%:<%%!%%:>%%1"^) do^
 for /f "tokens=2" %%%%1^| in ^("%%!=%%! %%3 %%:<%%!=%%:<%%!%%:>"^) do%# Factor-out in !!| the common caret protection for > < & ! definitions #%^
 for /f %%%%1^> in ^("%%:|>"^) do for /f %%%%1^< in ^("%%:|<"^) do for /f %%%%1^& in ^("%%:|&"^) do for /f %%%%1^| in ^("%%:||"^) do
These definitions generate the following macro values in any expansion mode:

Code: Select all

FOR!1=for %: in (%) do for /f %! in ("! =! ^^^!") do for /f %^^ in ("^ ^!=^!^^^^") do for %^> in (^>) do for %^< in (^<) do for %^& in (^&) do for %^| in (^|) do
FOR!2=for /f %^> in ("^^^^^^^^^^^^^^^") do for %: in (%) do for /f %! in ("! =! %>!") do for /f "tokens=2" %^^ in ("!=! ^^ ^^^!=^^^!%>^") do for /f %^| in ("^ ^^^!=^^^!%>") do for /f %^> in ("%|>") do for /f %^< in ("%|<") do for /f %^& in ("%|&") do for /f %^| in ("%||") do
FOR!3=for /f %^< in ("^^^^^^^^^^^^^^^") do for /f %^> in ("%<%<%<%<^^^") do for %: in (%) do for /f %! in ("! =! %>!") do for /f "tokens=2" %^^ in ("!=! ^^^^ %<!=%<!%>^") do for /f "tokens=2" %^| in ("!=! ^^^ %<!=%<!%>") do for /f %^> in ("%|>") do for /f %^< in ("%|<") do for /f %^& in ("%|&") do for /f %^| in ("%||") do
FOR!4=for /f %^< in ("^^^^^^^^^^^^^^^") do for /f %^< in ("%<%<%<%<^^^") do for /f %^> in ("%<%<%<%<^^^") do for %: in (%) do for /f %! in ("! =! %>!") do for /f "tokens=2" %^^ in ("!=! ^^^^^^^^ %<!=%<!%>^") do for /f "tokens=2" %^| in ("!=! ^^^^^^^ %<!=%<!%>") do for /f %^> in ("%|>") do for /f %^< in ("%|<") do for /f %^& in ("%|&") do for /f %^| in ("%||") do
If ever more than 3 levels of macro nesting depth are needed, expanding the series to a FOR!5 or further is relatively easy now. And this would generate a reasonably small macro, about 400 bytes long.

What I'd like to do now is to find a way to automatically keep track of the macro nesting depth, and automatically select the right macro version based on the actual depth.
This will probably be feasible only with delayed expansion enabled.

Squashman
Expert
Posts: 4480
Joined: 23 Dec 2011 13:59

Re: Automating the generation of macros usable within other macros

#5 Post by Squashman » 25 Feb 2024 14:37

Maybe we could ask Peter to put some of this on the main web page of the site. Or maybe we start a git repo.

Post Reply