Page 1 of 1

Recursively expand variables in strings

Posted: 13 Feb 2022 15:43
by Maylow
Hello everyone,
I wanted to share a function i just made to recursively expand variables in strings.
I've searched for it here and havn't come accross it on the forum so i thought to post it here.
It works for me for most and simple cases, i hope it may be of help to others.

Function to recursively expand variables in strings:

Code: Select all

:Expand Text [Rtn]
::  @param    (str) Text                    text with variables to expand
::  @param    (str) Rtn                     return variable
::  @return   (str) Text                    text with expanded variables
setlocal enableDelayedExpansion
for /f "tokens=1,* delims=!" %%b in ("%~1") do (
    if "!%%~b!" neq "" (
        call:Expand "!%%~b!%%~c" "Expand.Var"
    ) else (
        set "Expand.Var=!Expand.Var!%%~b"
        if "!%%~c!" neq "" call:Expand "!%%~c!" "Expand.Var"
    )
)
for /f "usebackq tokens=1,* delims==" %%b in (
    `set Expand.Var`) do (endlocal
    if "%~2" neq "" if "%~2" neq " " set "%~2=%%~c"
)
exit /b
Test code:

Code: Select all

@echo off
REM Code to test recursively expand variables in strings:
set "test1=a value 123"
set "test2=!test1! 456"
set "test3=!test2! 789"
set "test4=!test3! 10"
set "test5=start_string !test4! string_in_between !test2! some string"

echo(Normal expansion: "%test5% end"

setlocal enableDelayedExpansion
echo(Delayed expansion: "!test5! end"
endlocal

set MyRtn=
call:Expand "%test5% end" MyRtn
echo(Recursive expansion displayed with %%: "%MyRtn%"
set MyRtn=
setlocal enableDelayedExpansion
call:Expand "!test5! end" MyRtn
echo(Recursive expansion displayed with ^^!: "!MyRtn!"
endlocal
exit /b

:Expand Text [Rtn]
::  @param    (str) Text                    text with variables to expand
::  @param    (str) Rtn                     return variable
::  @return   (str) Text                    text with expanded variables
setlocal enableDelayedExpansion
for /f "tokens=1,* delims=!" %%b in ("%~1") do (
    if "!%%~b!" neq "" (
        call:Expand "!%%~b!%%~c" "Expand.Var"
    ) else (
        set "Expand.Var=!Expand.Var!%%~b"
        if "!%%~c!" neq "" call:Expand "!%%~c!" "Expand.Var"
    )
)
for /f "usebackq tokens=1,* delims==" %%b in (
    `set Expand.Var`) do (endlocal
    if "%~2" neq "" if "%~2" neq " " set "%~2=%%~c"
)
exit /b
Results:
Normal expansion: "start_string !test4! string_in_between !test2! some string end"
Delayed expansion: "start_string !test4! string_in_between !test2! some string end"
Results of recursive expansion displayed with %: "start_string a value 123 456 789 10 string_in_between a value 123 456 some string end"
Results of recursive expansion displayed with !: "start_string a value 123 456 789 10 string_in_between a value 123 456 some string end"

Edit: not thoroughly tested with strange characters though, but recursive expansion of simple variables works.

Update about special characters thus far:
Because of the recursion, special characters ^ ! % are resolved with each iteration. This means that literally meant special characters need as many escaping as the number of nestings and i can't figured how out to make this work at the moment.
Character ^ when needed literally, not to expand, can be used but is ultimately resvoled into nothing when nesting level of variables is deep.
Same applies to character %, it needs double escaping in strings when used literally: "%%%%" AND it can only be used BEFORE the first variable, like: set "test5=start_string %%%% !test4! string_in_between !test2! some string". It is because of the way the function currently works, all other percent signs are expanded.
Character ! cannot be used literally, even double escaping won't work and the function won't expand the variables in the string correctly.
Other special characters like `~&*[]()<>'" are no problem.

Above example adjusted with special characters %`~&*[]()<>'" but without exclamation mark ! special character:

Code: Select all

@echo off
REM Code to test recursively expand variables in strings:
set "test1=a value 123 ~`^^&[]()<>''"""
set "test2=!test1! 456 ~`^^&[]()<>''"""
set "test3=!test2! 789 ~`^^&[]()<>''"""
set "test4=!test3! 10 ~`^^&[]()<>''"""
set "test5=start_string %%%% ~`^^&[]()<>''"" !test4! string_in_between ~`^^&[]()<>''"" !test2! some string ~`^^&[]()<>''"""

echo(Normal expansion: "%test5% end"

setlocal enableDelayedExpansion
echo(Delayed expansion: "!test5! end"
endlocal

set MyRtn=
call:Expand "%test5% end" MyRtn
echo(Recursive expansion displayed with %%: "%MyRtn%"
set MyRtn=
setlocal enableDelayedExpansion
call:Expand "!test5! end" MyRtn
echo(Recursive expansion displayed with ^^!: "!MyRtn!"
endlocal
exit /b

:Expand Text [Rtn]
::  @param    (str) Text                    text with variables to expand
::  @param    (str) Rtn                     return variable
::  @return   (str) Text                    text with expanded variables
setlocal enableDelayedExpansion
for /f "tokens=1,* delims=!" %%b in ("%~1") do (
    if "!%%~b!" neq "" (
        call:Expand "!%%~b!%%~c" "Expand.Var"
    ) else (
        set "Expand.Var=!Expand.Var!%%~b"
        if "!%%~c!" neq "" call:Expand "!%%~c!" "Expand.Var"
    )
)
for /f "usebackq tokens=1,* delims==" %%b in (
    `set Expand.Var`) do (endlocal
    if "%~2" neq "" if "%~2" neq " " set "%~2=%%~c"
)
exit /b
Gives following results:
Normal expansion: "start_string %% ~`^^&[]()<>''"" !test4! string_in_between ~`^^&[]()<>''"" !test2! some string ~`^^&[]()<>''"" end"
Delayed expansion: "start_string %% ~`^^&[]()<>''"" !test4! string_in_between ~`^^&[]()<>''"" !test2! some string ~`^^&[]()<>''"" end"
Results of recursive expansion displayed with %: "start_string % ~`^&[]()<>''"" a value 123 ~`^^&[]()<>''"" 456 ~`^&[]()<>''"" 789 ~`^&[]()<>''"" 10 ~`&[]()<>''"" string_in_between ~`&[]()<>''"" a value 123 ~`^&[]()<>''"" 456 ~`&[]()<>''"" some string ~`&[]()<>''"" end"
Results of recursive expansion displayed with !: "start_string % ~`^&[]()<>''"" a value 123 ~`^^&[]()<>''"" 456 ~`^&[]()<>''"" 789 ~`^&[]()<>''"" 10 ~`&[]()<>''"" string_in_between ~`&[]()<>''"" a value 123 ~`^&[]()<>''"" 456 ~`&[]()<>''"" some string ~`&[]()<>''"" end"

Currently looking for way to have the function preserve literally meant special characters ^ % ! with each recursive call of the function. Using 'CALL set "Expand.Var=!Expand.Var!%%~b"' or 'CALL set "%~2=%%~c"' in the function does not solve the issue, neither does 'set "Expand.Var=!Expand.Var:%%=%%%%!"' solve it for percent signs.
Suggestions are welcome.

Edit: some typos.

Re: Recursively expand variables in strings

Posted: 13 Feb 2022 21:28
by Maylow
Found a workaround at least for the mentioned issue about literally meant special characters ! and %.

The updated function currently replaces #pc# with % percent sign characters.

Function:

Code: Select all

:Expand Text [Rtn]
::  @param    (str) Text                    text with variables to expand
::  @param    (str) Rtn                     return variable
::  @return   (str) Text                    text with expanded variables
::  Note: use #pc# in strings for literally meant percent sign characters, these are replaced by the function with percent sign characters.
::  Tip: use #em# in strings for literally meant exclamation marks characters, you can replace them in the returned result with exclamation mark characters.
setlocal enableDelayedExpansion
for /f "tokens=1,* delims=!" %%b in ("%~1") do (
    if "!%%~b!" neq "" (
        call:Expand "!%%~b!%%~c" "Expand.Var"
    ) else set "Expand.Var=!Expand.Var!%%~b"
    if "!%%~c!" neq "" call:Expand "!%%~c!" "Expand.Var"
)
set "Expand.Var=!Expand.Var:#pc#=%%!"
for /f "usebackq tokens=1,* delims==" %%b in (
    `set Expand.Var`) do (endlocal
    if "%~2" neq "" if "%~2" neq " " set "%~2=%%~c"
)
exit /b
The ! exclamation mark character can be changed in the same way with the returned result, i currently use #em#.

Together with the example:

Code: Select all

@echo off

REM Code to test recursively expand variables in strings:
set "test1=a value 123 #pc##em#~`^^&[]()<>''"""
set "test2=!test1! 456 #pc##em#~`^^&[]()<>''"""
set "test3=!test2! 789 #pc##em#~`^^&[]()<>''"""
set "test4=!test3! 10 #pc##em#~`^^&[]()<>''"""
set "test5=start_string #pc##em#~`^^&[]()<>''"" !test4! string_in_between #pc##em#~`^^&[]()<>''"" !test2! some string #pc##em#~`^^&[]()<>''"""

echo(Normal expansion: "%test5% end"

setlocal enableDelayedExpansion
echo(Delayed expansion: "!test5! end"
endlocal

set MyRtn=
call:Expand "%test5% end" MyRtn
set "MyRtn=%MyRtn:#em#=!%"
echo(Results of recursive expansion displayed with %%: "%MyRtn%"
set MyRtn=
setlocal enableDelayedExpansion
call:Expand "!test5! end" MyRtn
set "MyRtn=%MyRtn:#em#=^!%"
echo(Results of recursive expansion displayed with ^^!: "!MyRtn!"
endlocal
exit /b

:Expand Text [Rtn]
::  @param    (str) Text                    text with variables to expand
::  @param    (str) Rtn                     return variable
::  @return   (str) Text                    text with expanded variables
::  Note: use #pc# in strings for literally meant percent sign characters, these are replaced by the function with percent sign characters.
::  Tip: use #em# in strings for literally meant exclamation marks characters, you can replace them in the returned result with exclamation mark characters.
setlocal enableDelayedExpansion
for /f "tokens=1,* delims=!" %%b in ("%~1") do (
    if "!%%~b!" neq "" (
        call:Expand "!%%~b!%%~c" "Expand.Var"
    ) else set "Expand.Var=!Expand.Var!%%~b"
    if "!%%~c!" neq "" call:Expand "!%%~c!" "Expand.Var"
)
set "Expand.Var=!Expand.Var:#pc#=%%!"
for /f "usebackq tokens=1,* delims==" %%b in (
    `set Expand.Var`) do (endlocal
    if "%~2" neq "" if "%~2" neq " " set "%~2=%%~c"
)
exit /b
Gives the following results:
Normal expansion: "start_string #pc##em#~`^^&[]()<>''"" !test4! string_in_between #pc##em#~`^^&[]()<>''"" !test2! some string #pc##em#~`^^&[]()<>''"" end"
Delayed expansion: "start_string #pc##em#~`^^&[]()<>''"" !test4! string_in_between #pc##em#~`^^&[]()<>''"" !test2! some string #pc##em#~`^^&[]()<>''"" end"
Results of recursive expansion displayed with %: "start_string %!~`^&[]()<>''"" a value 123 %!~`^^&[]()<>''"" 456 %!~`^&[]()<>''"" 789 %!~`^&[]()<>''"" 10 %!~`&[]()<>''"" string_in_between %!~`&[]()<>''"" a value 123 %!~`^&[]()<>''"" 456 %!~`&[]()<>''"" some string %!~`&[]()<>''"" end"
Results of recursive expansion displayed with !: "start_string %!~`&[]()<>''"" a value 123 %!~`^&[]()<>''"" 456 %!~`&[]()<>''"" 789 %!~`&[]()<>''"" 10 %!~`&[]()<>''"" string_in_between %!~`&[]()<>''"" a value 123 %!~`&[]()<>''"" 456 %!~`&[]()<>''"" some string %!~`&[]()<>''"" end"

Still remaining is to preserve literally meant ^ characters.
Suggestions are welcome.

Re: Recursively expand variables in strings

Posted: 13 Feb 2022 21:51
by Maylow
Havn't found any solutions yet, so i recommended doing the same with ^ caret characters as with ! exclamation mark characters as i explained above.
In the example below i replace #ct# with ^ in the returned result.

Example:

Code: Select all

@echo off

REM Code to test recursively expand variables in strings:
set "test1=a value 123 #pc##em##ct#~`^^&[]()<>''"""
set "test2=!test1! 456 #pc##em##ct#~`^^&[]()<>''"""
set "test3=!test2! 789 #pc##em##ct#~`^^&[]()<>''"""
set "test4=!test3! 10 #pc##em##ct#~`^^&[]()<>''"""
set "test5=start_string #pc##em##ct#~`&[]()<>''"" !test4! string_in_between #pc##em##ct#~`^^&[]()<>''"" !test2! some string #pc##em##ct#~`^^&[]()<>''"""

echo(Normal expansion: "%test5% end"

setlocal enableDelayedExpansion
echo(Delayed expansion: "!test5! end"
endlocal

set MyRtn=
call:Expand "%test5% end" MyRtn
set "MyRtn=%MyRtn:#em#=!%"
set "MyRtn=%MyRtn:#ct#=^%"
echo(Results of recursive expansion displayed with %%: "%MyRtn%"
set MyRtn=
setlocal enableDelayedExpansion
call:Expand "!test5! end" MyRtn
set "MyRtn=%MyRtn:#em#=^!%"
set "MyRtn=!MyRtn:#ct#=^!"
echo(Results of recursive expansion displayed with ^^!: "!MyRtn!"
endlocal
exit /b

:Expand Text [Rtn]
::  @param    (str) Text                    text with variables to expand
::  @param    (str) Rtn                     return variable
::  @return   (str) Text                    text with expanded variables
::  Note: use #pc# in strings for literally meant percent sign characters, these are replaced by the function with percent sign characters.
::  Tip: use #em# in strings for literally meant exclamation marks characters, you can replace them in the returned result with exclamation mark characters.
::  Tip: use #ct# in strings for literally meant caret characters, you can replace them in the returned result with caret characters.
setlocal enableDelayedExpansion
for /f "tokens=1,* delims=!" %%b in ("%~1") do (
    if "!%%~b!" neq "" (
        call:Expand "!%%~b!%%~c" "Expand.Var"
    ) else set "Expand.Var=!Expand.Var!%%~b"
    if "!%%~c!" neq "" call:Expand "!%%~c!" "Expand.Var"
)
set "Expand.Var=!Expand.Var:#pc#=%%!"
for /f "usebackq tokens=1,* delims==" %%b in (
    `set Expand.Var`) do (endlocal
    if "%~2" neq "" if "%~2" neq " " set "%~2=%%~c"
)
exit /b
Gives the following results:
Normal expansion: "start_string #pc##em##ct#~`&[]()<>''"" !test4! string_in_between #pc##em##ct#~`^^&[]()<>''"" !test2! some string #pc##em##ct#~`^^&[]()<>''"" end"
Delayed expansion: "start_string #pc##em##ct#~`&[]()<>''"" !test4! string_in_between #pc##em##ct#~`^^&[]()<>''"" !test2! some string #pc##em##ct#~`^^&[]()<>''"" end"
Results of recursive expansion displayed with %: "start_string %!^~`&[]()<>''"" a value 123 %!^~`^^&[]()<>''"" 456 %!^~`^&[]()<>''"" 789 %!^~`^&[]()<>''"" 10 %!^~`&[]()<>''"" string_in_between %!^~`&[]()<>''"" a value 123 %!^~`^&[]()<>''"" 456 %!^~`&[]()<>''"" some string %!^~`&[]()<>''"" end"
Results of recursive expansion displayed with !: "start_string %!^~`&[]()<>''"" a value 123 %!^~`^&[]()<>''"" 456 %!^~`&[]()<>''"" 789 %!^~`&[]()<>''"" 10 %!^~`&[]()<>''"" string_in_between %!^~`&[]()<>''"" a value 123 %!^~`&[]()<>''"" 456 %!^~`&[]()<>''"" some string %!^~`&[]()<>''"" end"

Though not a real solution, the workaround at least works :)

Re: Recursively expand variables in strings

Posted: 14 Feb 2022 19:49
by Maylow
Note: the double colon : character also interferes with expanding variables recursively the way the function does. So i recommended to replace them too in strings with for instance #dc# en substitute them with double colons in the return value when needed literally.

To explain why I don't just simply use delayed expansion for everything in the first place, I have sets of variables globally defined to be available to other scripts, like the macros discussed in other topics.

For those interested in a use case scenario, see below.

Suppose a set of variables containing ANSI escape sequences with color values.
You might want to have an other set of variables containing a certain variety of color schemes so that another set of variables can use those color schemes to colorize different kinds of messages for instance.
Part of those messages are predefined while other parts are added when the event arises.
For example, when when an error occurs, the gathered information about the error is presented with an error message which uses a color scheme.

The code below demonstrates this.

Code: Select all

@echo off
REM Define ASCII characters:
set ^"\lf=^
%=empty=%
^"
set ^"\lfx=^^^%\lf%%\lf%^%\lf%%\lf%"
set ^"\n=^^^%\lf%%\lf%^%\lf%%\lf%^^"
for %%? in (
    1B:e
) do (
    for /f "tokens=1,* delims=:" %%g in ("%%?") do (
        for /f eol^=^%\lf%%\lf%^ delims^= %%j in (
            'forfiles /p "%~dp0." /m "%~nx0" /c "cmd /c echo(0x%%g"') do (
            set "\%%~h=%%~j"
        )
    )
)

REM Define COLOR and 4-bit ANSI color names and values:
for %%b in (
    "0 Black"
    "1 Blue"
    "2 Green"
    "3 Aqua"
    "4 Red"
    "5 Purple"
    "6 Yellow"
    "7 White"
    "8 Gray"
    "9 LightBlue"
    "A LightGreen"
    "B LightAqua"
    "C LightRed"
    "D LightPurple"
    "E LightYellow"
    "F BrightWhite"
    "30 Black FG"
    "31 Red FG"
    "32 Green FG"
    "33 Yellow FG"
    "34 Blue FG"
    "35 Purple FG"
    "36 Aqua FG"
    "37 White FG"
    "39 Reset FG"
    "40 Black BG"
    "41 Red BG"
    "42 Green BG"
    "43 Yellow BG"
    "44 Blue BG"
    "45 Purple BG"
    "46 Aqua BG"
    "47 White BG"
    "49 Reset BG"
    "90 Gray FG"
    "91 LightRed FG"
    "92 LightGreen FG"
    "93 LightYellow FG"
    "94 LightBlue FG"
    "95 LightPurple FG"
    "96 LightAqua FG"
    "97 BrightWhite FG"
    "100 Gray BG"
    "101 LightRed BG"
    "102 LightGreen BG"
    "103 LightYellow BG"
    "104 LightBlue BG"
    "105 LightPurple BG"
    "106 LightAqua BG"
    "107 BrightWhite BG"
) do (
    for /f "tokens=1,2,3" %%g in (%%b) do (
        if "%%~i" neq "" (
            set "C_%%~i_%%~g=%%~h"
            set "C_%%~i_%%~h=%\e%[%%~gm"
        ) else (
            set "C_%%~g=%%~h"
            set "C_%%~h=%%~g"
        )
    )
)
set "C_Reset=%\e%[39m%\e%[49m"

REM Define style profiles:
set "MyProfile.Style.Dev.ColorBG=%C_BG_Black%"
set "MyProfile.Style.Dev.ColorFG=%C_FG_LightGreen%"
set "MyProfile.Style.Tst.ColorBG=%C_BG_Black%"
set "MyProfile.Style.Tst.ColorFG=%C_FG_LightYellow%"
set "MyProfile.Style.Acc.ColorBG=%C_BG_Black%"
set "MyProfile.Style.Acc.ColorFG=%C_FG_BrightWhite%"
set "MyProfile.Style.Prd.ColorBG=%C_BG_Black%"
set "MyProfile.Style.Prd.ColorFG=%C_FG_White%"

REM Define style configuration:
REM You can change the profile in this sample from Dev, to Tst, to Acc, to Prd to see different results.
set "MyCfg.Style.Profile=MyProfile.Style.Dev"

REM Define some test values:
set "object=wheather"
set "opinion=fine"
set "person=me"

REM Define some test messages:
set "msg1=the !opinion! !object! makes !person! happy"
set "msg2=Yesterday I thought !msg1!"
set "msg3=!msg2!. But today I'm not interested."

REM Define error messages:
for %%b in (
    "100 Warning %C_FG_LightBlue%"
    "101 Failure %C_FG_LightPurple%"
    "102 Collapse %C_FG_LightRed%"
) do (
    for /f "tokens=1,2,3" %%g in (%%b) do (
        set "Error%%~g=%%~iERROR[%%~g] '%%~h'%C_Reset%"
    )
)

REM Define some error information:
set "ErrorMsg1=!%MyCfg.Style.Profile%.ColorBG!!%MyCfg.Style.Profile%.ColorFG!'Something happened'%C_Reset%"
set "ErrorMsg2=!ErrorMsg1! @%~nx0#dc#"
set "ErrorMsg3=!ErrorMsg2! 'take immediate action'"

setlocal enableDelayedExpansion
REM Display message solely relying on delayed expansion:
echo(!%MyCfg.Style.Profile%.ColorBG!!%MyCfg.Style.Profile%.ColorFG!!msg3!%C_Reset%

REM Display message with variables recursively expanded:
call:Expand "!%MyCfg.Style.Profile%.ColorBG!!%MyCfg.Style.Profile%.ColorFG!!msg3!%C_Reset%" MyRtn
echo(!MyRtn!

REM Simulate an error:
REM You can change the errorlevel in this sample from 100, to 101, to 102 to see different results.
cmd /d /c exit /b 100

REM React upon error:
if "%errorlevel%" neq "0" (
    REM Display message solely relying on delayed expansion:
    1>&2 echo(!Error%errorlevel%! !ErrorMsg3:#dc#=:!, or take a cup of coffee.
    
    REM Display message with variables recursively expanded:
    call:Expand "!Error%errorlevel%! !ErrorMsg3!, or take a cup of coffee." MyRtn
    1>&2 echo(!MyRtn:#dc#=:!
)
endlocal
exit /b


:Expand Text [Rtn]
::  @param    (str) Text                    text with variables to expand
::  @param    (str) Rtn                     return variable
::  @return   (str) Text                    text with expanded variables
setlocal enableDelayedExpansion
for /f "tokens=1,* delims=!" %%b in ("%~1") do (
    if "!%%~b!" neq "" (
        call:Expand "!%%~b!%%~c" "Expand.Var"
    ) else set "Expand.Var=!Expand.Var!%%~b"
    if "!%%~c!" neq "" call:Expand "!%%~c!" "Expand.Var"
)
set "Expand.Var=!Expand.Var:#pc#=%%!"
for /f "usebackq tokens=1,* delims==" %%b in (
    `set Expand.Var`) do (endlocal
    set "%~2=%%~c"
)
exit /b
This gives the following results with colors according to the chosen profile variable in the configuration variable:

!msg2!. But today I'm not interested.
Yesterday I thought the fine wheather makes me happy. But today I'm not interested.
ERROR[100] 'Warning' !ErrorMsg2! 'take immediate action', or take a cup of coffee.
ERROR[100] 'Warning' 'Something happened' @sample.cmd: 'take immediate action', or take a cup of coffee.

The first line is a message displayed relying solely on delayed expansion.
The second line is the same message as the first line but using the recursive expansion function.
The third line is an error message displayed relying solely on delayed expansion.
The fourth line is the same error message as the third line but using the recursive expansion function.

As you can see, delayed expansion does not expand endlessly and this is where the function may be convenient.

Re: Recursively expand variables in strings

Posted: 15 Feb 2022 07:14
by T3RRY
Where an extra level of expansion is required, it is generally simpler to use call or a for loop to expand component delayed variables of macros.

A short example of using call to trigger an extra parsing step for the sort of use case you propose:

Code: Select all

@Echo off
 For /f delims^= %%e in ('echo Prompt $E^|cmd')Do set "\E=%%e"
(Set LF=^


%= above empty lines required. =%)

 Set "errCode=(Call Echo(!\E![38;5;!Errorlevel!m%%error[!Errorlevel!]%%!\E![0m&Rem:)"
 Set "error[160]=Missing input. Input is mandatory."
 Set "error[190]=Invalid input. Integer Required."
 Set "error[137]=Scripting Error - Missing ReturVar."

 Setlocal EnableDelayedExpansion

 :input
  Set "input="
  Set /p "input=Enter a Number [1-255] or Gtr 255 to progress demo: "
  Call:validate input $Var || %errCode:Rem:=Goto:input%

  2>nul Set /A "1/(255/$var)" && (
 	Echo(%\E%[0m%$Var%: %\E%[38;5;%$Var%m {Color} %\E%[0m
 	Goto:input
  )

  Set /p "demo=Demo: Press Enter.!LF!"
  Call:validate input || %errCode%

 Endlocal & goto:Eof


 :validate integer returnVar
  Set "Arg1="
  Set "Arg1=!%~1!"
  If not Defined Arg1 Exit /b 160
  (Set Arg1) | %SystemRoot%\System32\Findstr.exe /RX ".*=[123456789][0123456789]*$" > nul || Exit /b 190
  2> nul Set "%~2=!Arg1!" || Exit /b 137
 Exit /b 0
A more advanced example of using for loops to expand reference components of macro's can be found at https://pastebin.com/x1YDZzd1 or seen at: https://www.youtube.com/watch?v=QSS7S5M38D4

Using for loops is far more efficient than call. If you have a multitude of reference variables, you can expand them in a single for /f loop with tokens options to reference use as the reference. The above linked demo script achieves a rediculous framerate thanks to for loop expansion, as it completely avoids the need to use Call which is comparitevly slow.

Re: Recursively expand variables in strings

Posted: 16 Feb 2022 02:49
by Maylow
T3RRY wrote:
15 Feb 2022 07:14
Where an extra level of expansion is required, it is generally simpler to use call or a for loop to expand component delayed variables of macros.

A short example of using call to trigger an extra parsing step for the sort of use case you propose:

Code: Select all

@Echo off
 For /f delims^= %%e in ('echo Prompt $E^|cmd')Do set "\E=%%e"
(Set LF=^


%= above empty lines required. =%)

 Set "errCode=(Call Echo(!\E![38;5;!Errorlevel!m%%error[!Errorlevel!]%%!\E![0m&Rem:)"
 Set "error[160]=Missing input. Input is mandatory."
 Set "error[190]=Invalid input. Integer Required."
 Set "error[137]=Scripting Error - Missing ReturVar."

 Setlocal EnableDelayedExpansion

 :input
  Set "input="
  Set /p "input=Enter a Number [1-255] or Gtr 255 to progress demo: "
  Call:validate input $Var || %errCode:Rem:=Goto:input%

  2>nul Set /A "1/(255/$var)" && (
 	Echo(%\E%[0m%$Var%: %\E%[38;5;%$Var%m {Color} %\E%[0m
 	Goto:input
  )

  Set /p "demo=Demo: Press Enter.!LF!"
  Call:validate input || %errCode%

 Endlocal & goto:Eof


 :validate integer returnVar
  Set "Arg1="
  Set "Arg1=!%~1!"
  If not Defined Arg1 Exit /b 160
  (Set Arg1) | %SystemRoot%\System32\Findstr.exe /RX ".*=[123456789][0123456789]*$" > nul || Exit /b 190
  2> nul Set "%~2=!Arg1!" || Exit /b 137
 Exit /b 0
A more advanced example of using for loops to expand reference components of macro's can be found at https://pastebin.com/x1YDZzd1 or seen at: https://www.youtube.com/watch?v=QSS7S5M38D4

Using for loops is far more efficient than call. If you have a multitude of reference variables, you can expand them in a single for /f loop with tokens options to reference use as the reference. The above linked demo script achieves a rediculous framerate thanks to for loop expansion, as it completely avoids the need to use Call which is comparitevly slow.

CALL can be used as extra level of expansion. However, it also performs an additional step in escaping special characters like the caret character which may be unwanted.
Handling extra levels can indeed be done with FOR, though you would have to know how many levels.
The function i wrote is for when one does not know the number of levels.
Still, thanks for the comment, much appreciated!