ReturnVar macro revisited

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
carlsomo
Posts: 91
Joined: 02 Oct 2012 17:21

ReturnVar macro revisited

#1 Post by carlsomo » 19 Oct 2013 14:49

A while back jeb came up with a ReturnVar Macro single that could return poison character strings across the endlocal barrier and worked with both enabled and disabled delayed expansion with a helper function that was required if called with expansion disabled. It could also handle CR and LineFeeds.

http://www.dostips.com/forum/viewtopic.php?f=3&t=4827&hilit=return+macro

I came up with a modification that seems to work without a helper function when called with expansion enabled or disabled but I had to simplify it without CR and LineFeed handling. Can this concept even handle CR and LF??

TestReturnVar.bat

Code: Select all

:TestReturnVar.bat
@echo off
setlocal
Set "Return=%~1"
if not defined Return (
  Echo Please specify a return variable as first argument
  goto :eof
)
Echo(Variable entered will be returned in %Return% variable
call :getvar var="Enter %Return%: "
call :eco "%Return%=" var /n
choice /n /C EDQ /m "Select return with expansion [E]nabled or [D]isabled or [Q]uit"
set/a Expansion=%errorlevel%
rem define the macro
setlocal disabledelayedexpansion
call :ReturnVar
goto :%Expansion%

:1
setlocal enabledelayedexpansion
%ReturnVar% %~1 var 3
call :eco "%~1=" %~1 /n
goto :eof

:2
rem expansion is already disabled here
%ReturnVar% %~1 var 2
call :eco "%~1=" %~1 /n
goto :eof

:3
echo(Variable %Return% is undefined or unchanged
call :eco "%~1=" %~1 /n
goto :eof

:ReturnVar Macro
@if defined ReturnVar @exit /b -1

:initReturnVar
@if "!" equ "" (
  >&2 @echo ERROR: ReturnVar must be initialized with delayed expansion disabled
  @exit /b 1
)

@set LF=^


::  Above 2 blank lines are critical - Do not remove  ::
@set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

@for /f "usebackq delims= " %%C in (`copy /z "%~f0" nul`) do @set "CR=%%C"

@REM ** ReturnVar <resultVariable> <TransferVariable> [endlocalCount] by jeb
@REM ** return a <TransferVariable> over one or more endlocal barriers to a resultVariable
@REM ** endlocalCount default=1, is the number of endlocals that should be executed
@REM ** All special characters can be transferd from TranferVariable to the result variable,
@REM ** even "<>&|!^"
@REM ** The result is correct, independent of the delayed expansion mode after the endlocals
@set ^"ReturnVar=@for %%# in (1 2) do @if %%#==2 @(%\n%
  setlocal EnableDelayedExpansion%\n%
  set/a safeReturn_count=0%\n%
  for %%C in (!args!) do @(%\n%
    set "safeReturn[!safeReturn_count!]=%%~C" ^& set /a safeReturn_count+=1%\n%
  )%\n%
  if not defined safeReturn[2] set "safeReturn[2]=1"%\n%
  set /a safeReturn[2]+=2%\n%
  for /f "delims=" %%T in ("!safeReturn[1]!") do @set "_#_$_safeReturn=!%%T!"%\n%
  for /f "delims=" %%N in (""!safeReturn[0]!"") do @(%\n%
    for /f tokens^^=^^1^^*^^ delims^^=^^=^^ eol^^= %%U in ('set _#_$_safeReturn') do @(%\n%
      for /l %%n in (1 1 !safeReturn[2]!) do @endlocal%\n%
      set "%%~N=%%V"%\n%
    )%\n%
  )%\n%
) else @setlocal ^& @set args="
@exit /b 0

:GetVar
:: SYNTAX: GetVar ^<EnVar^> [="User Prompt: "] [length] [/m]
::
:: Assigns User input to the Environment variable passed as 1st argument
:: Allows unbalance quotes and 'poison' characters mixed with quotes
:: The only editing key available to the User is 'backspace'
:: The user string entered is set in the Environment variable, 'Envar'
:: If no arguments are passed the routine quits, stand alone version displays help
:: The fisrt argument is mandatory, the rest are optional
:: To display a variable with Poison chars and quotes use: Eco.bat
:: To mask input with '*'s place /m at the very end of the argument list
:: Must be called with delayed expansion disabled for '!', '^' and '&' in string
::
:: Example:
::
:: GetVar.bat Pa$$word="Enter your 'Poison Password' string: " len /m
:: Eco.bat "Your Poison Password is: '" Pa$$word "' with length: " len
:::
::: Dependencies - None           Author: Carl with ideas borrowed from Dostips.com
:::
  if "%~1"=="" goto :End_GetVar
  SetLocal DisableDelayedExpansion
  set "Masked="
  echo(%*|find /i " /m">nul
  if %errorlevel% equ 0 set "Masked=*"
  For /F %%# In ('"Prompt;$H&For %%# in (1) Do Rem"') Do Set "BS=%%#"
  Set "Line="
  if /i "%~2" neq "/m" <Nul set/p=".%BS% %BS%%~2"

:Char_Loop_
  Set "Key="
  For /F "delims=" %%# In (
     'Xcopy /L /W "%~f0" "%~f0" 2^>Nul'
  ) Do If Not Defined Key Set "Key=%%#"
  Set "Key=%Key:~-1%"
  SetLocal EnableDelayedExpansion
  If Not Defined Key ( Rem Enter pressed
     echo(
     If not Defined Line EndLocal&EndLocal&(
        If Defined %~1 Set "%~1="
        If Defined %~3 Set/a "%~3=0
     )&exit/b 0
     For /F delims^=^ eol^= %%# In ("!Line!") Do (
        EndLocal&EndLocal&(
           If not "%~3"=="" Set/a "%~3=%length%"
           If Not "%~1"=="" (Set "%~1=%%#")
           exit/b %length%
        )
     )
  )
  If %BS%==^%Key% (
     Set "Key="
     If Defined Line set/a length-=1& Set "Line=!Line:~0,-1!"& Set /P "=%BS% %BS%" <Nul
  ) Else (
     If defined Masked (Set "Display=*") Else (Set "Display=!Key:~-1!")
     set/a length+=1& Set /p=".%BS%!Display!" <Nul
  )
  If Not Defined Line (
     EndLocal& Set/a length=1& Set "Line=%Key%"
  ) Else For /F delims^=^ eol^= %%# In ("!Line!") Do (
     EndLocal& Set/a length=%length%& Set "Line=%%#%Key%"
  )
  Goto :Char_Loop_
:End_GetVar
EndLocal&exit/b 0

:Eco "Here is a char: " char " and a string: " EnvarString /n "This is line two."
::
:: Echo's strings mixed with environment variables with or without CR/LF or embed CR/LF inside
:: Environment variables must be passed by name (not reference) and may contain poison chars
:: Quoted strings are echo'd as is and may have leading or trailing spaces
:: To place a newline (CF/LF) use '/n' unquoted
:::
::: Dependencies - None
:::
  @SetLocal DisableDelayedExpansion
  @For /F %%# In ('"Prompt;$H&For %%# in (1) Do Rem"') Do @Set "BS=%%# %%#"
  @SetLocal EnableDelayedExpansion
  :Eco_Loop
  @if "%~1"=="" @EndLocal& @EndLocal& @exit/b
  @if /i "/n" equ "%~1" @(
    echo(
  ) else @if not defined %1 @(
    set "string=.%BS%%~1"
    for /f delims^=^ eol^= %%# in ("!string!") do @EndLocal& @set/p="%%#" <nul
    SetLocal EnableDelayedExpansion
  ) else @( <nul set/p=".%BS%!%~1!")
  @shift& @goto :Eco_Loop
:End_Eco

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

Re: ReturnVar macro revisited

#2 Post by jeb » 20 Oct 2013 14:07

Hi carlsomo,

nice to see that someone tries to build a better macro. :)

But your solution doesn't work. :cry:

It fails if you return to an enabled context, from an enabled or disabled context.
IMHO this is the most complicated case at all, as you have to handle exclamation marks and carets.

I reduce your code to the necessary test cases.
The results are for my test string of ^a!"^b
Output wrote:Test #1 DDE
result=^a!"^b
----------------------
Test #2 EDE
result=a"b


Code: Select all

:TestReturnVar.bat
@echo off
setlocal DisableDelayedExpansion
call :ReturnVar

set "var=^a!"^^b"

set var
echo -------------------------
call :Test_Dis
echo -------------------------
call :Test_Ena
exit /b

:Test_Ena
echo Test #2 EDE
setlocal enabledelayedexpansion
setlocal
%ReturnVar% result var 1
set result
endlocal
goto :eof

:Test_Dis
echo Test #1 DDE
setlocal Disabledelayedexpansion
setlocal
%ReturnVar% result var 1
set result
endlocal
goto :eof

:ReturnVar Macro
@if defined ReturnVar @exit /b -1

:initReturnVar
@if "!" equ "" (
  >&2 @echo ERROR: ReturnVar must be initialized with delayed expansion disabled
  @exit /b 1
)

@set LF=^


::  Above 2 blank lines are critical - Do not remove  ::
@set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

@for /f "usebackq delims= " %%C in (`copy /z "%~f0" nul`) do @set "CR=%%C"

@REM ** ReturnVar <resultVariable> <TransferVariable> [endlocalCount] by jeb
@REM ** return a <TransferVariable> over one or more endlocal barriers to a resultVariable
@REM ** endlocalCount default=1, is the number of endlocals that should be executed
@REM ** All special characters can be transferd from TranferVariable to the result variable,
@REM ** even "<>&|!^"
@REM ** The result is correct, independent of the delayed expansion mode after the endlocals
@set ^"ReturnVar=@for %%# in (1 2) do @if %%#==2 @(%\n%
  setlocal EnableDelayedExpansion%\n%
  set/a safeReturn_count=0%\n%
  for %%C in (!args!) do @(%\n%
    set "safeReturn[!safeReturn_count!]=%%~C" ^& set /a safeReturn_count+=1%\n%
  )%\n%
  if not defined safeReturn[2] set "safeReturn[2]=1"%\n%
  set /a safeReturn[2]+=2%\n%
  for /f "delims=" %%T in ("!safeReturn[1]!") do @set "_#_$_safeReturn=!%%T!"%\n%
  for /f "delims=" %%N in (""!safeReturn[0]!"") do @(%\n%
    for /f tokens^^=^^1^^*^^ delims^^=^^=^^ eol^^= %%U in ('set _#_$_safeReturn') do @(%\n%
      for /l %%n in (1 1 !safeReturn[2]!) do @endlocal%\n%
      set "%%~N=%%V"%\n%
    )%\n%
  )%\n%
) else @setlocal ^& @set args="
@exit /b 0


jeb

carlsomo
Posts: 91
Joined: 02 Oct 2012 17:21

Re: ReturnVar macro revisited

#3 Post by carlsomo » 20 Oct 2013 18:04

OK, here's another crack at it:

Code: Select all

:TestReturnVar.bat
@echo off
setlocal DisableDelayedExpansion
call :ReturnVar

set "var=^a!"^^b"

set var
echo -------------------------
call :Test_Dis
echo -------------------------
call :Test_Ena
exit /b

:Test_Ena
echo Test #2 EDE
setlocal enabledelayedexpansion
setlocal
%ReturnVar% result var 1
set result
endlocal
goto :eof

:Test_Dis
echo Test #1 DDE
setlocal Disabledelayedexpansion
setlocal
%ReturnVar% result var 1
set result
endlocal
goto :eof

:ReturnVar Macro
@if defined ReturnVar @exit /b -1

:initReturnVar
@if "!" equ "" (
  >&2 @echo ERROR: ReturnVar must be initialized with delayed expansion disabled
  @exit /b 1
)

@set LF=^


::  Above 2 blank lines are critical - Do not remove  ::
@set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

@for /f "usebackq delims= " %%C in (`copy /z "%~f0" nul`) do @set "CR=%%C"

@REM ** ReturnVar <resultVariable> <TransferVariable> [endlocalCount] by jeb
@REM ** return a <TransferVariable> over one or more endlocal barriers to a resultVariable
@REM ** endlocalCount default=1, is the number of endlocals that should be executed
@REM ** All special characters can be transferd from TranferVariable to the result variable,
@REM ** even "<>&|!^"
@REM ** The result is correct, independent of the delayed expansion mode after the endlocals
@set ^"ReturnVar=@for %%# in (1 2) do @if %%#==2 @(%\n%
  setlocal EnableDelayedExpansion%\n%
  set/a safeReturn_count=0%\n%
  for %%C in (!args!) do @(%\n%
    set "safeReturn[!safeReturn_count!]=%%~C" ^& set /a safeReturn_count+=1%\n%
  )%\n%
  if not defined safeReturn[2] set "safeReturn[2]=1"%\n%
  set /a safeReturn[2]+=2%\n%
  for /f "delims=" %%T in ("!safeReturn[1]!") do @set "_#_$_safeReturn=!%%T!"%\n%
  if not defined _ndf (%\n%
    set ^"safeReturn_Ena=!_#_$_safeReturn:"=""q!"%\n%
    set "safeReturn_Ena=!safeReturn_Ena:^=^^!"%\n%
    call set "safeReturn_Ena=%%safeReturn_Ena:^!=""c^!%%"%\n%
    set "safeReturn_Ena=!safeReturn_Ena:""c=^!"%\n%
    set ^"safeReturn_Ena=!safeReturn_Ena:""q="!"%\n%
    set "_#_$_safeReturn=!safeReturn_Ena!"%\n%
  )%\n%
  for /f "delims=" %%N in (""!safeReturn[0]!"") do @(%\n%
    for /f tokens^^=^^1^^*^^ delims^^=^^=^^ eol^^= %%U in ('set _#_$_safeReturn') do @(%\n%
      for /l %%n in (1 1 !safeReturn[2]!) do @endlocal%\n%
      set "%%~N=%%V"%\n%
    )%\n%
  )%\n%
) else @setlocal ^& set "_ndf=!" ^& @set args="
@exit /b 0


F:\>TestReturnVar.bat
var=^a!"^b
-------------------------
Test #1 DDE
result=^a!"^b
-------------------------
Test #2 EDE
result=^a!"^b

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

Re: ReturnVar macro revisited

#4 Post by jeb » 21 Oct 2013 00:29

Ok, your code is now more like the original,
but here are the next test results :wink:

Output wrote:Test now test1=^a!"^b
Test #1 Disable/Disable result='^a!"^b'
Test #2 Disable/Enable result='^^a^!"^^b'
Test #3 Enable/Disable result='a"b'
Test #4 Enable/Enable result='^a!"^b'

Test now test2=caret^
Test #5 Disable/Disable result='caret^'
Test #6 Disable/Enable result='caret^^'
Test #7 Enable/Disable result='caret^'
Test #8 Enable/Enable result='caret^^'


I suppose, it's more tricky than you thought. :)

Code: Select all

:TestReturnVar.bat
@echo off
setlocal DisableDelayedExpansion
call :ReturnVar

set /a test_cnt=0
set "test1=^a!"^^b"
set "test2=caret^"

call :Test_Both test1
call :Test_Both test2
exit /b

:Test_Both
setlocal EnableDelayedExpansion
echo Test now %1=!%1!
endlocal
call :Test_context Disable Disable %1
call :Test_context Disable Enable  %1
call :Test_context Enable  Disable %1
call :Test_context Enable  Enable  %1
echo(
exit /b

:Test_context
set /a test_cnt+=1
setlocal %1DelayedExpansion
setlocal %2DelayedExpansion
%ReturnVar% result %3 1

REM Output the result
setlocal enabledelayedexpansion
echo Test #!test_cnt! %1/%2 result='!result!'
endlocal

endlocal
goto :eof

:ReturnVar Macro
@if defined ReturnVar @exit /b -1

:initReturnVar
@if "!" equ "" (
  >&2 @echo ERROR: ReturnVar must be initialized with delayed expansion disabled
  @exit /b 1
)

@set LF=^


::  Above 2 blank lines are critical - Do not remove  ::
@set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

@for /f "usebackq delims= " %%C in (`copy /z "%~f0" nul`) do @set "CR=%%C"

@REM ** ReturnVar <resultVariable> <TransferVariable> [endlocalCount] by jeb
@REM ** return a <TransferVariable> over one or more endlocal barriers to a resultVariable
@REM ** endlocalCount default=1, is the number of endlocals that should be executed
@REM ** All special characters can be transferd from TranferVariable to the result variable,
@REM ** even "<>&|!^"
@REM ** The result is correct, independent of the delayed expansion mode after the endlocals
@set ^"ReturnVar=@for %%# in (1 2) do @if %%#==2 @(%\n%
  setlocal EnableDelayedExpansion%\n%
  set/a safeReturn_count=0%\n%
  for %%C in (!args!) do @(%\n%
    set "safeReturn[!safeReturn_count!]=%%~C" ^& set /a safeReturn_count+=1%\n%
  )%\n%
  if not defined safeReturn[2] set "safeReturn[2]=1"%\n%
  set /a safeReturn[2]+=2%\n%
  for /f "delims=" %%T in ("!safeReturn[1]!") do @set "_#_$_safeReturn=!%%T!"%\n%
  if not defined _ndf (%\n%
    set ^"safeReturn_Ena=!_#_$_safeReturn:"=""q!"%\n%
    set "safeReturn_Ena=!safeReturn_Ena:^=^^!"%\n%
    call set "safeReturn_Ena=%%safeReturn_Ena:^!=""c^!%%"%\n%
    set "safeReturn_Ena=!safeReturn_Ena:""c=^!"%\n%
    set ^"safeReturn_Ena=!safeReturn_Ena:""q="!"%\n%
    set "_#_$_safeReturn=!safeReturn_Ena!"%\n%
  )%\n%
  for /f "delims=" %%N in (""!safeReturn[0]!"") do @(%\n%
    for /f tokens^^=^^1^^*^^ delims^^=^^=^^ eol^^= %%U in ('set _#_$_safeReturn') do @(%\n%
      for /l %%n in (1 1 !safeReturn[2]!) do @endlocal%\n%
      set "%%~N=%%V"%\n%
    )%\n%
  )%\n%
) else @setlocal ^& set "_ndf=!" ^& @set args="
@exit /b 0

carlsomo
Posts: 91
Joined: 02 Oct 2012 17:21

Re: ReturnVar macro revisited

#5 Post by carlsomo » 21 Oct 2013 20:49

OK jeb, this is pure evil! :twisted:

To be fair, I think when %ReturnVar% is called then all the setlocals above it in the designated subroutine or bat should be accounted for in the %ReturnVar% 'call'

I think I found a way to make it work, but it requires an extra step. _ndf should be set before the first setlocal that changes the expansion environment so the routine knows what environment it is returning a result to. All the setlocals after that need to be accounted for in the %ReturnVar% call. Then it works:

OUTPUT:
Test now test1=^a!"^b
Test #1 Disable/Disable result='^a!"^b'
Test #2 Disable/Enable result='^a!"^b'
Test #3 Enable/Disable result='^a!"^b'
Test #4 Enable/Enable result='^a!"^b'

Test now test2=caret^
Test #5 Disable/Disable result='caret^'
Test #6 Disable/Enable result='caret^'
Test #7 Enable/Disable result='caret^'
Test #8 Enable/Enable result='caret^'

Code: Select all

:TestReturnVar.bat
@echo off
setlocal DisableDelayedExpansion
call :ReturnVar

set /a test_cnt=0
set "test1=^a!"^^b"
set "test2=caret^"

call :Test_Both test1
call :Test_Both test2
exit /b

:Test_Both
setlocal EnableDelayedExpansion
echo Test now %1=!%1!
endlocal
call :Test_context Disable Disable %1
call :Test_context Disable Enable  %1
call :Test_context Enable  Disable %1
call :Test_context Enable  Enable  %1
echo(
exit /b

:Test_context
set /a test_cnt+=1
setlocal
set "_ndf=!"
setlocal %1DelayedExpansion
setlocal %2DelayedExpansion
%ReturnVar% result %3 2

REM Output the result
setlocal enabledelayedexpansion
echo Test #!test_cnt! %1/%2 result='!result!'
endlocal

endlocal
goto :eof

:ReturnVar Macro
@if defined ReturnVar @exit /b -1

:initReturnVar
@if "!" equ "" (
  >&2 @echo ERROR: ReturnVar must be initialized with delayed expansion disabled
  @exit /b 1
)

@set LF=^


::  Above 2 blank lines are critical - Do not remove  ::
@set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

@for /f "usebackq delims= " %%C in (`copy /z "%~f0" nul`) do @set "CR=%%C"

@REM ** ReturnVar <resultVariable> <TransferVariable> [endlocalCount] by jeb
@REM ** return a <TransferVariable> over one or more endlocal barriers to a resultVariable
@REM ** endlocalCount default=1, is the number of endlocals that should be executed
@REM ** All special characters can be transferd from TranferVariable to the result variable,
@REM ** even "<>&|!^"
@REM ** The result is correct, independent of the delayed expansion mode after the endlocals
@set ^"ReturnVar=@for %%# in (1 2) do @if %%#==2 @(%\n%
  setlocal EnableDelayedExpansion%\n%
  set/a safeReturn_count=0%\n%
  for %%C in (!args!) do @(%\n%
    set "safeReturn[!safeReturn_count!]=%%~C" ^& set /a safeReturn_count+=1%\n%
  )%\n%
  if not defined safeReturn[2] set "safeReturn[2]=1"%\n%
  set /a safeReturn[2]+=2%\n%
  for /f "delims=" %%T in ("!safeReturn[1]!") do @set "_#_$_safeReturn=!%%T!"%\n%
  if not defined _ndf (%\n%
    set ^"safeReturn_Ena=!_#_$_safeReturn:"=""q!"%\n%
    set "safeReturn_Ena=!safeReturn_Ena:^=^^!"%\n%
    call set "safeReturn_Ena=%%safeReturn_Ena:^!=""c^!%%"%\n%
    set "safeReturn_Ena=!safeReturn_Ena:""c=^!"%\n%
    set ^"safeReturn_Ena=!safeReturn_Ena:""q="!"%\n%
    set "_#_$_safeReturn=!safeReturn_Ena!"%\n%
  )%\n%
  for /f "delims=" %%N in (""!safeReturn[0]!"") do @(%\n%
    for /f tokens^^=^^1^^*^^ delims^^=^^=^^ eol^^= %%U in ('set _#_$_safeReturn') do @(%\n%
      for /l %%n in (1 1 !safeReturn[2]!) do @endlocal%\n%
      set "%%~N=%%V" !%\n%
    )%\n%
  )%\n%
) else @setlocal ^& @set args="
@exit /b 0

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

Re: ReturnVar macro revisited

#6 Post by jeb » 22 Oct 2013 11:17

Now your test, tests always the same, _ndf is always "!" as it's in the disabled context.
But it's not a problem, your code works even if you repair this.

carlsomo wrote:_ndf should be set before the first setlocal that changes the expansion environment

You could also detect the flag at the end, after the macro done all endlocals.

But now I added two simple tests.
One empty string and one string beginning with equal signs. :twisted:

Have fun :)
Output wrote:Test now test3=''
Die Umgebungsvariable "_#_$_safeReturn" ist nicht definiert.
Test #9 Disable/Disable result=''
Die Umgebungsvariable "_#_$_safeReturn" ist nicht definiert.
Test #10 Disable/Enable result=''
Test #11 Enable/Disable result='"="'
Test #12 Enable/Enable result='"="'

Test now test4='===Equals'
Test #13 Disable/Disable result='Equals'
Test #14 Disable/Enable result='Equals'
Test #15 Enable/Disable result='Equals'
Test #16 Enable/Enable result='Equals'


Code: Select all

:TestReturnVar.bat
@echo off
setlocal DisableDelayedExpansion
call :ReturnVar

set /a test_cnt=0
set "test1=^a!"^^b"
set "test2=caret^"
set "test3="
set "test4====Equals"

for /L %%n in (1 1 4) do (
   call :Test_Both test%%n
)
exit /b

:Test_Both
setlocal EnableDelayedExpansion
echo Test now                    %1='!%1!'
endlocal
call :Test_context Disable Disable %1
call :Test_context Disable Enable  %1
call :Test_context Enable  Disable %1
call :Test_context Enable  Enable  %1
echo(
exit /b

:Test_context
set /a test_cnt+=1
setlocal %1DelayedExpansion
set "_ndf=!"
setlocal %2DelayedExpansion
%ReturnVar% result %3 1

REM Output the result
setlocal enabledelayedexpansion
set "format1=  Test #!test_cnt! "
set "format2=%1/%2  "
echo !format1:~0,10! !format2:~0,15! result='!result!'
endlocal

endlocal
goto :eof

carlsomo
Posts: 91
Joined: 02 Oct 2012 17:21

Re: ReturnVar macro revisited

#7 Post by carlsomo » 22 Oct 2013 22:16

This seems to work as long as someone does not use ascii dec 246 very often 8)
I presume you have more quirks to toss at me?

**EDIT**
took your advice and got rid of the _ndf requirement and tested after the endlocals, thx

OUTPUT:
F:\>set "test1=^a!"^b"

F:\>set "test2=caret^"

F:\>set "test3="

F:\>set "test4====Equals"

F:\>set test5=&"&^&!!!^^%^!

Test now test1='^a!"^b'
Test #1 Disable/Disable result='^a!"^b'
Test #2 Disable/Enable result='^a!"^b'
Test #3 Enable/Disable result='^a!"^b'
Test #4 Enable/Enable result='^a!"^b'

Test now test2='caret^'
Test #5 Disable/Disable result='caret^'
Test #6 Disable/Enable result='caret^'
Test #7 Enable/Disable result='caret^'
Test #8 Enable/Enable result='caret^'

Test now test3=''
Test #9 Disable/Disable result=''
Test #10 Disable/Enable result=''
Test #11 Enable/Disable result=''
Test #12 Enable/Enable result=''

Test now test4='===Equals'
Test #13 Disable/Disable result='===Equals'
Test #14 Disable/Enable result='===Equals'
Test #15 Enable/Disable result='===Equals'
Test #16 Enable/Enable result='===Equals'

Test now test5='&"&^&!!!^^%^!'
Test #17 Disable/Disable result='&"&^&!!!^^%^!'
Test #18 Disable/Enable result='&"&^&!!!^^%^!'
Test #19 Enable/Disable result='&"&^&!!!^^%^!'
Test #20 Enable/Enable result='&"&^&!!!^^%^!'

Code: Select all

:TestReturnVar.bat
@echo off
setlocal DisableDelayedExpansion
call :ReturnVar
rem set ret
set /a test_cnt=0
echo on
set "test1=^a!"^^b"
set "test2=caret^"
set "test3="
set "test4====Equals"
@rem        &"&^&!!!^^%^!
set  test5=^&"&^&!!!^^%%^!
@echo off&echo(

for /L %%n in (1 1 5) do (
   call :Test_Both test%%n
)
exit /b

:Test_Both
setlocal EnableDelayedExpansion
echo Test now                    %1='!%1!'
endlocal
call :Test_context Disable Disable %1
call :Test_context Disable Enable  %1
call :Test_context Enable  Disable %1
call :Test_context Enable  Enable  %1
echo(
exit /b

:Test_context
set /a test_cnt+=1
setlocal %1DelayedExpansion
rem set "_ndf=!"
setlocal %2DelayedExpansion
%ReturnVar% result %3 1

REM Output the result
setlocal enabledelayedexpansion
set "format1=  Test #!test_cnt! "
set "format2=%1/%2  "
echo !format1:~0,10! !format2:~0,15! result='!result!'
endlocal

endlocal
goto :eof

:ReturnVar Macro
@if defined ReturnVar @exit /b -1

:initReturnVar
@if "!" equ "" (
  >&2 @echo ERROR: ReturnVar must be initialized with delayed expansion disabled
  @exit /b 1
)

@set LF=^


::  Above 2 blank lines are critical - Do not remove  ::
@set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

@for /f "usebackq delims= " %%C in (`copy /z "%~f0" nul`) do @set "CR=%%C"

@REM ** ReturnVar <resultVariable> <TransferVariable> [endlocalCount] by jeb and carl
@REM ** return a <TransferVariable> over one or more endlocal barriers to a resultVariable
@REM ** endlocalCount default=1, is the number of endlocals that should be executed
@REM ** All special characters can be transferd from TranferVariable to the result variable,
@REM ** even "<>&|!^"
@REM ** The result is correct, independent of the delayed expansion mode after the endlocals
@set ^"ReturnVar=@for %%# in (1 2) do @if %%#==2 @(%\n%
  setlocal EnableDelayedExpansion%\n%
  set/a safeReturn_count=0%\n%
  for %%C in (!args!) do @(%\n%
    set "safeReturn[!safeReturn_count!]=%%~C" ^& set /a safeReturn_count+=1%\n%
  )%\n%
  if not defined safeReturn[2] set "safeReturn[2]=1"%\n%
  set /a safeReturn[2]+=2%\n%
  for /f "delims=" %%T in ("!safeReturn[1]!") do @set "_#_$_safeReturn=÷!%%T!"%\n%
  set ^"safeReturn_Ena=!_#_$_safeReturn:"=""q!"%\n%
  set "safeReturn_Ena=!safeReturn_Ena:^=^^!"%\n%
  call set "safeReturn_Ena=%%safeReturn_Ena:^!=""c^!%%"%\n%
  set "safeReturn_Ena=!safeReturn_Ena:""c=^!"%\n%
  set ^"safeReturn_Ena=!safeReturn_Ena:""q="!"%\n%
  set "safeReturn_Ena=!safeReturn_Ena:~1!"%\n%
  for /f "delims=" %%N in (""!safeReturn[0]!"") do @(%\n%
    for /f "delims=" %%E in (""!safeReturn_Ena!"") do @(%\n%
      for /f tokens^^=^^1^^*^^ delims^^=^^÷^^ eol^^= %%U in ('set _#_$_safeReturn') do @(%\n%
        for /l %%n in (1 1 !safeReturn[2]!) do @endlocal%\n%
        if "!" equ "" (set "%%~N=%%~E" !) else (set "%%~N=%%V" !)%\n%
      )%\n%
    )%\n%
  )%\n%
) else @setlocal ^& @set args="
@exit /b 0

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

Re: ReturnVar macro revisited

#8 Post by jeb » 23 Oct 2013 14:59

carlsomo wrote:I presume you have more quirks to toss at me?

I give my best :D

But this time I have only some questions and constructive criticism.

carlsomo wrote:This seems to work as long as someone does not use ascii dec 246 very often 8)

Hmm, I don't like solutions which works mostly, they will make trouble sooner or later.

Questions about:

Code: Select all

for /f tokens^^=^^1^^*^^ delims^^=^^÷^^ eol^^= %%U in ('set _#_$_safeReturn') do

Why you disable the eol character at all? The line will always begin with "_#_$_safeReturn=" :wink:

Btw. 'set _#_$_safeReturn' creates a new cmd.exe context, this is ~300 times :!: slower than a FOR/F string access.
Why you don't use

Code: Select all

for /f "delims=" %%U in (""!_#_$_safeReturn!"") do ... %~U

This will always enclose the string in quotes, so you don't have problems with empty strings, nor with EOL, neither you need a prefix character like ASCII 246.
And the %~U will remove the enclosing quotes.

Code: Select all

if "!" equ "" (set "%%~N=%%~E" !) else (set "%%~N=%%V" !)%\n%

Looks much better tahn the old ndf approach, but in the part **(set "%%~N=%%V" !)** the exclamation can be dropped, in a DisabledDE it's useless.

And with only two lines you could add the support for carriage return, without any trouble.

The linefeed is still tricky, especialy for DDE.
I can't find any solution which works for both linefeeds and CRs,
only for one type of character or for all CR's but only up to 32 LF's.

carlsomo
Posts: 91
Joined: 02 Oct 2012 17:21

Re: ReturnVar macro revisited

#9 Post by carlsomo » 23 Oct 2013 19:04

Dear jeb,
To be quite honest I am working more from trial and error than intimate knowledge of the many nuances of the DOS interpreter. When something works I tend to stick with it and ask questions later when I attempt to optimize without screwing things up. My initial stab at this problem was to see if the requirement for the 'helper' routine could be eliminated and I decided to omit CR and LF handling to make it simpler. The set statement in the for loop turned into a workable solution that I have used to write a couple macros that work from the command line and I stuck with it. I agree that this solution is not nearly as elegant nor as the well thought out as the code you grace this board with and I admit that I am still in a major learning process. I would like to take this opportunity to thank you for your time and patience as well as the challenges you gave me. I am a student here but I wish to contribute as much as I can while I am learning from the best people I have found in dealing with DOS challenges. Your constructive criticism is much appreciated.
carl

carlsomo
Posts: 91
Joined: 02 Oct 2012 17:21

Re: ReturnVar macro revisited

#10 Post by carlsomo » 23 Oct 2013 19:37

When I remove eol^^= from the for loop 'set line' I get an error message:

in was unexpected at this time.

test3='"="'
is displayed on enable/disable and enable/enable unless I single quote the **("!_#_$_safeReturn!")** in the for loop replacement line you suggested and use %%U rather than %%~U in the assignment. It is now working without the 246 character and may be 300 times faster?? I will next look into the CR and LF problem.

Code: Select all

:TestReturnVar.bat
@echo off
setlocal DisableDelayedExpansion
call :ReturnVar
rem set ret
set /a test_cnt=0
echo on
set "test1=^a!"^^b"
set "test2=caret^"
set "test3="
set "test4====Equals"
@rem        &"&^&!!!^^%^!
set  test5=^&"&^&!!!^^%%^!
@echo off&echo(

for /L %%n in (1 1 5) do (
   call :Test_Both test%%n
)
exit /b

:Test_Both
setlocal EnableDelayedExpansion
echo Test now                    %1='!%1!'
endlocal
call :Test_context Disable Disable %1
call :Test_context Disable Enable  %1
call :Test_context Enable  Disable %1
call :Test_context Enable  Enable  %1
echo(
exit /b

:Test_context
set /a test_cnt+=1
setlocal %1DelayedExpansion
rem set "_ndf=!"
setlocal %2DelayedExpansion
%ReturnVar% result %3 1

REM Output the result
setlocal enabledelayedexpansion
set "format1=  Test #!test_cnt! "
set "format2=%1/%2  "
echo !format1:~0,10! !format2:~0,15! result='!result!'
endlocal

endlocal
goto :eof

:ReturnVar Macro
@if defined ReturnVar @exit /b -1

:initReturnVar
@if "!" equ "" (
  >&2 @echo ERROR: ReturnVar must be initialized with delayed expansion disabled
  @exit /b 1
)

@set LF=^


::  Above 2 blank lines are critical - Do not remove  ::
@set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
@rem ÷
@for /f "usebackq delims= " %%C in (`copy /z "%~f0" nul`) do @set "CR=%%C"

@REM ** ReturnVar <resultVariable> <TransferVariable> [endlocalCount] by jeb and carl
@REM ** return a <TransferVariable> over one or more endlocal barriers to a resultVariable
@REM ** endlocalCount default=1, is the number of endlocals that should be executed
@REM ** All special characters can be transferd from TranferVariable to the result variable,
@REM ** even "<>&|!^"
@REM ** The result is correct, independent of the delayed expansion mode after the endlocals
@set ^"ReturnVar=@for %%# in (1 2) do @if %%#==2 @(%\n%
  setlocal EnableDelayedExpansion%\n%
  set/a safeReturn_count=0%\n%
  for %%C in (!args!) do @(%\n%
    set "safeReturn[!safeReturn_count!]=%%~C" ^& set /a safeReturn_count+=1%\n%
  )%\n%
  if not defined safeReturn[2] set "safeReturn[2]=1"%\n%
  set /a safeReturn[2]+=2%\n%
  for /f "delims=" %%T in ("!safeReturn[1]!") do @set "_#_$_safeReturn=!%%T!"%\n%
  set ^"safeReturn_Ena=!_#_$_safeReturn:"=""q!"%\n%
  set "safeReturn_Ena=!safeReturn_Ena:^=^^!"%\n%
  call set "safeReturn_Ena=%%safeReturn_Ena:^!=""c^!%%"%\n%
  set "safeReturn_Ena=!safeReturn_Ena:""c=^!"%\n%
  set ^"safeReturn_Ena=!safeReturn_Ena:""q="!"%\n%
  for /f "delims=" %%N in (""!safeReturn[0]!"") do @(%\n%
    for /f "delims=" %%E in (""!safeReturn_Ena!"") do @(%\n%
      for /f "delims=" %%U in ("!_#_$_safeReturn!") do @(%\n%
        for /l %%n in (1 1 !safeReturn[2]!) do @endlocal%\n%
        if "!" equ "" (set "%%~N=%%~E" !) else (set "%%~N=%%U")%\n%
      )%\n%
    )%\n%
  )%\n%
) else @setlocal ^& @set args="
@exit /b 0


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

Re: ReturnVar macro revisited

#11 Post by jeb » 24 Oct 2013 02:41

carlsomo wrote:When I remove eol^^= from the for loop 'set line' I get an error message:
in was unexpected at this time.

I suppose that one extra space is needed just before the **in**

carlsomo wrote:test3='"="'
is displayed on enable/disable and enable/enable unless I single quote the **("!_#_$_safeReturn!")** in the for loop replacement line you suggested and use %%U rather than %%~U in the assignment.

But with single quotes it doesn't work, it only seems so :!:

I changed the test function

Code: Select all

:Test_context
set /a test_cnt+=1
setlocal %1DelayedExpansion
set "result=not set by macro"
setlocal %2DelayedExpansion
%ReturnVar% result %3 1

REM Output the result
setlocal enabledelayedexpansion
if "!result!"=="!%~3!" (
   set "postfix=OK"
) ELSE (
    set postfix=FAIL
)
set "format1=  Test #!test_cnt! "
set "format2=%1/%2  "
echo !format1:~0,10! !format2:~0,15! result='!result!'    - !postfix!
endlocal

endlocal
goto :eof


Output wrote:Test now test3=''
Test #9 Disable/Disable result='not set by macro' - FAIL
Test #10 Disable/Enable result='not set by macro' - FAIL
Test #11 Enable/Disable result='not set by macro' - FAIL
Test #12 Enable/Enable result='not set by macro' - FAIL


With the line

Code: Select all

for /f "delims=" %%U in (""!_#_$_safeReturn!"") do @(%\n%

You get
Output wrote:Test now test3=''
Test #9 Disable/Disable result='' - OK
Test #10 Disable/Enable result='' - OK
Test #11 Enable/Disable result='"="' - FAIL
Test #12 Enable/Enable result='"="' - FAIL

But it has nothing to do with %%U in this case, it's only that now the result will be set at all.

It's only false for the EnabledDE case, as in **safeReturn_Ena** is the wrong content of **"="**

carlsomo
Posts: 91
Joined: 02 Oct 2012 17:21

Re: ReturnVar macro revisited

#12 Post by carlsomo » 31 Oct 2013 22:28

jeb
I cannot explain why this works on my computer (win7) or on my work computer with XP but it seems to work for this series of tests. It should not work and I did not expect it to work but here it is:

Code: Select all

:TestReturnVar.bat
@echo off
setlocal DisableDelayedExpansion
if not defined ReturnVar call :ReturnVar
set ret
set /a test_cnt=0
echo on
set "test1=^a!"^^b"
set "test2=caret^"
set "test3="
set "test4====Equals"
@rem        &"&^&!!!^^%^!
set  test5=^&"&^&!!!^^%%^!
setlocal enabledelayedexpansion
set "test6=Hello &^^ "^^^^  ^&" world^!!CR!*!LF!X"
@echo off&echo(

for /L %%n in (1 1 6) do (
   call :Test_Both test%%n
)
exit /b

:Test_Both
setlocal EnableDelayedExpansion
echo Test now                    %1='!%1!'
endlocal
call :Test_context Disable Disable %1
call :Test_context Disable Enable  %1
call :Test_context Enable  Disable %1
call :Test_context Enable  Enable  %1
echo(
exit /b

:Test_context
set /a test_cnt+=1
setlocal %1DelayedExpansion
rem set "_ndf=!"
setlocal %2DelayedExpansion
%ReturnVar% result %3 1

REM Output the result
setlocal enabledelayedexpansion
set "format1=  Test #!test_cnt! "
set "format2=%1/%2  "
echo !format1:~0,10! !format2:~0,15! result='!result!'
endlocal

endlocal
goto :eof

:ReturnVar Macro
@if defined ReturnVar @exit /b -1

:initReturnVar
@if "!" equ "" (
  >&2 @echo ERROR: ReturnVar must be initialized with delayed expansion disabled
  @exit /b 1
)

@set LF=^


::  Above 2 blank lines are critical - Do not remove  ::
@set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

@for /f "usebackq delims= " %%C in (`copy /z "%~f0" nul`) do @set "CR=%%C"

@REM ** ReturnVar <resultVariable> <TransferVariable> [endlocalCount] by jeb and carl
@REM ** return a <TransferVariable> over one or more endlocal barriers to a resultVariable
@REM ** endlocalCount default=1, is the number of endlocals that should be executed
@REM ** All special characters can be transferd from TranferVariable to the result variable,
@REM ** even "<>&|!^"
@REM ** The result is correct, independent of the delayed expansion mode after the endlocals
@set ^"ReturnVar=@for %%# in (1 2) do @if %%#==2 @(%\n%
  setlocal EnableDelayedExpansion%\n%
  set/a safeReturn_count=0%\n%
  for %%C in (!args!) do @(%\n%
    set "safeReturn[!safeReturn_count!]=%%~C" ^& set /a safeReturn_count+=1%\n%
  )%\n%
  if not defined safeReturn[2] set "safeReturn[2]=1"%\n%
  set /a safeReturn[2]+=2%\n%
  for /f "delims=" %%T in ("!safeReturn[1]!") do @set "_#_$_safeReturn=a!%%T!"%\n%
  set ^"_#_$_safeReturn=!_#_$_safeReturn:"=""q!"%\n%
  FOR /F %%R in ("!CR! #") DO @set "_#_$_safeReturn=!_#_$_safeReturn:%%~R=""r!"%\n%
  FOR %%L in ("!LF!") DO @set "_#_$_safeReturn=!_#_$_safeReturn:%%~L=""n!"%\n%
  set "_#_$_safeReturn=!_#_$_safeReturn:^=^^!"%\n%
  call set "_#_$_safeReturn=%%_#_$_safeReturn:^!=""c^!%%"%\n%
  set "_#_$_safeReturn=!_#_$_safeReturn:""c=^!"%\n%
  set ^"_#_$_safeReturn=!_#_$_safeReturn:""q="!"%\n%
  for %%L in ("!LF!") do @(%\n%
    for /f "delims=" %%N in (""!safeReturn[0]!"") do @(%\n%
      for /f "delims=" %%E in (""!_#_$_safeReturn!"") do @(%\n%
        for /l %%n in (1 1 !safeReturn[2]!) do @endlocal%\n%
        if "!" neq "" setlocal enabledelayedexpansion ^& set "dis=1"%\n%
        set "%%~N=%%~E" !%\n%
        set "%%~N=!%%~N:""n=%%~L!"%\n%
        FOR /F %%R in ("!CR! #") DO @set "%%~N=!%%~N:""r=%%R!"%\n%
        set "%%~N=!%%~N:~1!"%\n%
        if defined dis for /f "delims=" %%A in (!%%N!) do @endlocal ^& @set "%%~N=%%A"%\n%
      )%\n%
    )%\n%
  )%\n%
) else @setlocal ^& @set args="
@exit /b 0

carlsomo
Posts: 91
Joined: 02 Oct 2012 17:21

Re: ReturnVar macro revisited

#13 Post by carlsomo » 01 Nov 2013 01:41

I figured out my problem and my final solution to the issue of avoiding a helper function is this:

Use the following ReturnVar macro form any environment without CR and LF in the transfer variable.
If one wishes to pass a transfer variable that contains CR or LF characters then set the environment
to delayed expansion before using the ReturnVar macro and it should work.

Code: Select all

::——————______________________——————::
::                                  ::
::——————|  ReturnVar  Macro  |——————::
::                                  ::
::——————¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯——————::

@if "%~1"=="/?" @(set "GNL=") else @if "!" equ "" @(
  >&2 echo ERROR: ReturnVar must be initialized with delayed expansion disabled
  exit /b 1
) else @if not defined ReturnVar @(set "GNL=>nul ") else @exit/b 0

@echo(%GNL%
@echo(ReturnVar Macro:%GNL%
@echo(%GNL%
@echo(%%ReturnVar%% ^<resultVariable^> ^<TransferVariable^> [endlocalCount] by jeb and carl%GNL%
@echo(Return a ^<TransferVariable^> over multiple endlocal barriers to a resultVariable%GNL%
@echo(endlocalCount default=1, is the number of endlocals that should be executed%GNL%
@echo(All special characters can be transferd from TranferVariable to the%GNL%
@echo(result variable, even "<>&|!^"%GNL%
@echo(The result is correct, independent of the delayed expansion environment%GNL%
@echo(after the endlocals, and in effect from the calling routine's environment%GNL%
@echo(Carriage Return and LineFeed characters can only be processed if the Macro is%GNL%
@echo(utilised from an expansion enabled environment%GNL%
@if not defined GNL @exit/b 1
@set "GNL="
@set LF=^


::  Above 2 blank lines are critical - Do not remove  ::
@set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
@for /f "usebackq delims= " %%C in (`copy /z "%~f0" nul`) do @set "CR=%%C"

:ReturnVar Macro
@set ^"ReturnVar=@for %%# in (1 2) do @if %%#==2 @(%\n%
  setlocal EnableDelayedExpansion%\n%
  set/a safeReturn_count=0%\n%
  for %%C in (!args!) do @(%\n%
    set "safeReturn[!safeReturn_count!]=%%~C" ^& set /a safeReturn_count+=1%\n%
  )%\n%
  if not defined safeReturn[2] set "safeReturn[2]=1"%\n%
  set /a safeReturn[2]+=2%\n%
  for /f "delims=" %%T in ("!safeReturn[1]!") do @set "_#_$_safeReturn=a!%%T!"%\n%
  set ^"_#_$_safeReturn=!_#_$_safeReturn:"=""q!"%\n%
  FOR /F %%R in ("!CR! #") DO @set "_#_$_safeReturn=!_#_$_safeReturn:%%~R=""r!"%\n%
  FOR %%L in ("!LF!") DO @set "_#_$_safeReturn=!_#_$_safeReturn:%%~L=""n!"%\n%
  set "_#_$_safeReturn=!_#_$_safeReturn:^=^^!"%\n%
  call set "_#_$_safeReturn=%%_#_$_safeReturn:^!=""c^!%%"%\n%
  set "_#_$_safeReturn=!_#_$_safeReturn:""c=^!"%\n%
  set ^"_#_$_safeReturn=!_#_$_safeReturn:""q="!"%\n%
  for %%L in ("!LF!") do @(%\n%
    for /f "delims=" %%N in (""!safeReturn[0]!"") do @(%\n%
      for /f "delims=" %%E in (""!_#_$_safeReturn!"") do @(%\n%
        for /l %%n in (1 1 !safeReturn[2]!) do @endlocal%\n%
        if "!" neq "" setlocal enabledelayedexpansion ^& set "_#_$_dis=1"%\n%
        set "%%~N=%%~E" !%\n%
        set "%%~N=!%%~N:""n=%%~L!"%\n%
        FOR /F %%R in ("!CR! #") DO @set "%%~N=!%%~N:""r=%%R!"%\n%
        set "%%~N=!%%~N:~1!"%\n%
        if defined _#_$_dis (%\n%
          for /f delims^^=^^ eol^^=  %%A in (""!%%~N!"") do @(%\n%
            endlocal ^& @set "%%~N=%%~A"%\n%
          )%\n%
        )%\n%
      )%\n%
    )%\n%
  )%\n%
) else @setlocal ^& @set args="
@exit /b 0

carlsomo
Posts: 91
Joined: 02 Oct 2012 17:21

Re: ReturnVar macro revisited

#14 Post by carlsomo » 01 Nov 2013 17:19

This is the result with the latest iteration of ReturnVar with jeb's latest test:

Test now test1='^a!"^b'
Test #1 Disable/Disable result='^a!"^b' - OK
Test #2 Disable/Enable result='^a!"^b' - OK
Test #3 Enable/Disable result='^a!"^b' - OK
Test #4 Enable/Enable result='^a!"^b' - OK

Test now test2='caret^'
Test #5 Disable/Disable result='caret^' - OK
Test #6 Disable/Enable result='caret^' - OK
Test #7 Enable/Disable result='caret^' - OK
Test #8 Enable/Enable result='caret^' - OK

Test now test3=''
Test #9 Disable/Disable result='' - OK
Test #10 Disable/Enable result='' - OK
Test #11 Enable/Disable result='' - OK
Test #12 Enable/Enable result='' - OK

Test now test4='===Equals'
Test #13 Disable/Disable result='===Equals' - OK
Test #14 Disable/Enable result='===Equals' - OK
Test #15 Enable/Disable result='===Equals' - OK
Test #16 Enable/Enable result='===Equals' - OK

Test now test5='&"&^&!!!^^%^!'
Test #17 Disable/Disable result='&"&^&!!!^^%^!' - OK
Test #18 Disable/Enable result='&"&^&!!!^^%^!' - OK
Test #19 Enable/Disable result='&"&^&!!!^^%^!' - OK
Test #20 Enable/Enable result='&"&^&!!!^^%^!' - OK

*est now test6='Hello &^ "^ &" world!
X'
Test #21 Disable/Disable result='X"' - FAIL
Test #22 Disable/Enable result='X"' - FAIL
* Test #23 Enable/Disable result='Hello &^ "^ &" world!
X' - OK
* Test #24 Enable/Enable result='Hello &^ "^ &" world!
X' - OK

I cannot see a way to handle this macro call from disabled expansion with LF in the string and avoid the helper function call, but everything else works now (I think). As I see it the workaround is to only call returnvar with expansion enabled if there are LF chars in the transfer variable. I am not saying 'UNCLE' quite yet but more brain power than I have expended is necessary.

carlsomo
Posts: 91
Joined: 02 Oct 2012 17:21

Re: ReturnVar macro revisited

#15 Post by carlsomo » 04 Nov 2013 01:37

This routine passes the latest jeb test but it does not work with some other tests with bat to bat calls with expansion disabled nor to the typically disabled command line:

Code: Select all

:TestReturnVar.bat
@echo off
setlocal DisableDelayedExpansion
if not defined ReturnVar call :ReturnVar
set ret
set /a test_cnt=0
echo on
set "test1=^a!"^^b"
set "test2=caret^"
set "test3="
set "test4====Equals"
@rem        &"&^&!!!^^%^!
set  test5=^&"&^&!!!^^%%^!
setlocal enabledelayedexpansion
set "test6=Hello &^^ "^^^^  ^&" world^!!CR!*!LF!X"
@echo off&echo(

for /L %%n in (1 1 6) do (
   call :Test_Both test%%n
)
exit /b

:Test_Both
setlocal EnableDelayedExpansion
echo Test now                    %1='!%1!'
endlocal
call :Test_context Disable Disable %1
call :Test_context Disable Enable  %1
call :Test_context Enable  Disable %1
call :Test_context Enable  Enable  %1
echo(
exit /b

:Test_context
set /a test_cnt+=1
setlocal %1DelayedExpansion
set "result=not set by macro"
setlocal %2DelayedExpansion
%ReturnVar% result %3 1

REM Output the result
setlocal enabledelayedexpansion
if "!result!"=="!%~3!" (
   set "postfix=OK"
) ELSE (
    set postfix=FAIL
)
set "format1=  Test #!test_cnt! "
set "format2=%1/%2  "
echo !format1:~0,10! !format2:~0,15! result='!result!'    - !postfix!
endlocal

endlocal
goto :eof

:ReturnVar Macro
@if defined ReturnVar @exit /b -1

:initReturnVar
@if "!" equ "" (
  >&2 @echo ERROR: ReturnVar must be initialized with delayed expansion disabled
  @exit /b 1
)

@set LF=^


::  Above 2 blank lines are critical - Do not remove  ::
@set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

@for /f "usebackq delims= " %%C in (`copy /z "%~f0" nul`) do @set "CR=%%C"

@set ^"ReturnVar=@for %%# in (1 2) do @if %%#==2 @(%\n%
  setlocal EnableDelayedExpansion%\n%
  set/a safeReturn_count=0%\n%
  for %%C in (!args!) do @(%\n%
    set "safeReturn[!safeReturn_count!]=%%~C" ^& set /a safeReturn_count+=1%\n%
  )%\n%
  if not defined safeReturn[2] set "safeReturn[2]=1"%\n%
  set /a safeReturn[2]+=2%\n%
  for /f "delims=" %%T in ("!safeReturn[1]!") do @set "_#_$_safeReturn=a!%%T!"%\n%
  set ^"_#_$_safeReturn=!_#_$_safeReturn:"=""q!"%\n%
  FOR /F %%R in ("!CR! #") DO @set "_#_$_safeReturn=!_#_$_safeReturn:%%~R=""r!"%\n%
  FOR %%L in ("!LF!") DO @set "_#_$_safeReturn=!_#_$_safeReturn:%%~L=""n!"%\n%
  set "_#_$_safeReturn=!_#_$_safeReturn:^=^^!"%\n%
  call set "_#_$_safeReturn=%%_#_$_safeReturn:^!=""c^!%%"%\n%
  set "_#_$_safeReturn=!_#_$_safeReturn:""c=^!"%\n%
  set ^"_#_$_safeReturn=!_#_$_safeReturn:""q="!"%\n%
  for %%L in ("!LF!") do @(%\n%
    for /f "delims=" %%N in (""!safeReturn[0]!"") do @(%\n%
      for /f "delims=" %%E in (""!_#_$_safeReturn!"") do @(%\n%
        for /l %%n in (1 1 !safeReturn[2]!) do @endlocal%\n%
        if "!" neq "" setlocal enabledelayedexpansion ^& set "_#_$_dis=1"%\n%
        set "%%~N=%%~E" !%\n%
        set "%%~N=!%%~N:""n=%%~L!"%\n%
        FOR /F %%R in ("!CR! #") DO @set "%%~N=!%%~N:""r=%%R!"%\n%
        set "%%~N=!%%~N:~1!"%\n%
        if defined _#_$_dis for /f "delims=" %%A in (!%%N!) do @endlocal ^& @set "%%~N=%%A"%\n%
      )%\n%
    )%\n%
  )%\n%
) else @setlocal ^& @set args="
@exit /b 0

OUTPUT:
F:\Utilities>set "test1=^a!"^b"

F:\Utilities>set "test2=caret^"

F:\Utilities>set "test3="

F:\Utilities>set "test4====Equals"

F:\Utilities>set test5=&"&^&!!!^^%^!

F:\Utilities>setlocal enabledelayedexpansion

F:\Utilities>set "test6=Hello &^^ "^^ &" world^!!CR!*!LF!X"

Test now test1='^a!"^b'
Test #1 Disable/Disable result='^a!"^b' - OK
Test #2 Disable/Enable result='^a!"^b' - OK
Test #3 Enable/Disable result='^a!"^b' - OK
Test #4 Enable/Enable result='^a!"^b' - OK

Test now test2='caret^'
Test #5 Disable/Disable result='caret^' - OK
Test #6 Disable/Enable result='caret^' - OK
Test #7 Enable/Disable result='caret^' - OK
Test #8 Enable/Enable result='caret^' - OK

Test now test3=''
Test #9 Disable/Disable result='' - OK
Test #10 Disable/Enable result='' - OK
Test #11 Enable/Disable result='' - OK
Test #12 Enable/Enable result='' - OK

Test now test4='===Equals'
Test #13 Disable/Disable result='===Equals' - OK
Test #14 Disable/Enable result='===Equals' - OK
Test #15 Enable/Disable result='===Equals' - OK
Test #16 Enable/Enable result='===Equals' - OK

Test now test5='&"&^&!!!^^%^!'
Test #17 Disable/Disable result='&"&^&!!!^^%^!' - OK
Test #18 Disable/Enable result='&"&^&!!!^^%^!' - OK
Test #19 Enable/Disable result='&"&^&!!!^^%^!' - OK
Test #20 Enable/Enable result='&"&^&!!!^^%^!' - OK

*est now test6='Hello &^ "^ &" world!
X'
* Test #21 Disable/Disable result='Hello &^ "^ &" world!
X' - OK
* Test #22 Disable/Enable result='Hello &^ "^ &" world!
X' - OK
* Test #23 Enable/Disable result='Hello &^ "^ &" world!
X' - OK
* Test #24 Enable/Enable result='Hello &^ "^ &" world!
X' - OK

Post Reply