Page 1 of 1

return over an endlocal barrier with a macro

Posted: 06 Aug 2013 17:16
by jeb
Hi,

some time ago the safe return technic was developed, to return any text over an endlocal barrier.

But it has one big disadvantage, you need to copy the part to each function, as it can't be used in a general subfunction.

The cause is simple.
If the safe return would reside in a subfunction like :generalReturn this can't remove the SETLOCAL contexts, as a function can only remove the own SETLOCALs but not the ones, defined outside of the function

Code: Select all

@echo off
set var=origin
setlocal
set var=second
call :test
echo %var%
exit /b

:test
endlocal
exit /b

This code outputs second not origin as the endlocal in the :test function has no effect.

So I decide to write a macro for the return technic.
But this is also a bit tricky, as at some points (escaping exclamation marks) you need to use percent expansion, which will not work in a macro.
You could use a CALL, but then you can't place a single escape caret in front of the exclamation marks anymore.

And later you get the problem to decide, if the context you return to is a disabled or enabled one.
For the enabled context, it's possible to solve it inside the macro, but for the disabled context it seems to be impossible.
So I decide to call in that case a helper function.

Long explanation, but simply to use without the need to understand anything of the macro.

A function can use the macro

Code: Select all

:myFunction <returnVariable>
setlocal EnableDelayedExpansion
set "var=Hello &^^ "^^^^  ^&" world^!!CR!*!LF!X"
%jebReturn% %1 var
exit /b


But it can also be used in a normal FOR loop

Code: Select all

setlocal DisableDelayedExpansion
set "line="
for /L %%n in (1 1 3) do (
   setlocal DisableDelayedExpansion
   set "text=critical&<>| !" ^&^<^>^| ^^ !"
   setlocal EnableDelayedExpansion
   set full=!line!!text!
   %jebReturn% line full 2   
   setlocal EnableDelayedExpansion
   echo result=!line!
   endlocal
)


You only need to initialize the macro and when using the macro you can optional define the number of ENDLOCAL barriers to overcome.

Code: Select all

@echo off
setlocal DisableDelayedExpansion
call :initReturn
rem ** Now the macro can be used

setlocal EnableDelayedExpansion

call :test result
echo !result!

:test
setlocal EnableDelayedExpansion
setlocal EnableDelayedExpansion
setlocal EnableDelayedExpansion
set "var=Hello &^^ "^^^^  ^&" world^!!CR!*!LF!X"
%jebReturn% result var 3
exit /b

:::------------------
:initReturn
set LF=^


set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

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

REM ** jebReturn <resultVariable> <TranferVariable> [endlocalCount]
REM ** return a <TranferVariable> 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, even CR and LF and also "<>&|!^"
REM ** The result is correct, independent of the delayed expansion mode after the endlocals
set ^"jebReturn=for %%# in (1 2) do if %%#==2 (%\n%
   setlocal EnableDelayedExpansion%\n%
   set safeReturn_count=0%\n%
   for %%C in (!args!) do set "safeReturn[!safeReturn_count!]=%%~C" ^& set /a safeReturn_count+=1%\n%
   if not defined safeReturn[2] set "safeReturn[2]=1"%\n%
   set /a safeReturn[2]+=1%\n%
   for /F "delims=" %%T in ("!safeReturn[1]!") DO set "safeReturn_Ena=!%%T!"%\n%
   set ^"safeReturn_Ena=!safeReturn_Ena:"=""q!"%\n%
   FOR /F %%R in ("!CR! #") DO set "safeReturn_Ena=!safeReturn_Ena:%%~R=""r!"%\n%
   FOR %%L in ("!LF!") DO set "safeReturn_Ena=!safeReturn_Ena:%%~L=""n!"%\n%
   set "safeReturn_Dis=!safeReturn_Ena!"%\n%
   set "safeReturn_Ena=!safeReturn_Ena:^=^^!"%\n%
   set "path="%\n%
   set "pathExt=;"%\n%
   call set "safeReturn_Ena=%%safeReturn_Ena:^!=""c^!%%"%\n%
   set "safeReturn_Ena=!safeReturn_Ena:""c=^!"%\n%
   FOR %%L in ("!LF!") DO (%\n%
      for /F "delims=" %%N in (""!safeReturn[0]!"") DO (%\n%
         for /F "delims=" %%D in (""!safeReturn_Dis!"") DO (%\n%
            for /F "delims=" %%E in (""!safeReturn_Ena!"") DO (%\n%
               FOR /L %%n in (1 1 !safeReturn[2]!) do endlocal%\n%
               if "!"=="" (%\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:""q="!"%\n%
               ) ELSE (%\n%
                  set "%%~N=%%~D"%\n%
                  call :__disDelayedReturn %%~N%\n%
               )%\n%
            )%\n%
         )%\n%
      )%\n%
   )%\n%
) else set args="
exit /b

REM ** Helper function for correct returning when delayed expansion is disabled
:__disDelayedReturn
setlocal EnableDelayedExpansion
set "safeReturn_text=!%~1!"
set "safeReturn_text=!safeReturn_text:%%=%%~2!"
set "safeReturn_text=!safeReturn_text:""n=%%~L!"
set "safeReturn_text=!safeReturn_text:""r=%%~3!"
set "safeReturn_text=!safeReturn_text:""q=%%~4!"
FOR %%L in ("!LF!") DO (
   FOR /F "tokens=1-4" %%2 in (^"%% !CR! """") DO (
      endlocal
      set "%1=%safeReturn_text%"
   )
)
exit /b


hope it helps
jeb

Re: return over an endlocal barrier with a macro

Posted: 07 Aug 2013 09:33
by jeb
I forgot to link to the macro of dbenham New Function Template - return ANY string safely and easily.

The main difference between the macros is that I use the new technic with appended arguments.
And the macro only needs one invokation, therefore I need a complete other solution, as Dave's macro.

Re: return over an endlocal barrier with a macro

Posted: 07 Aug 2013 20:09
by carlsomo
Wow jeb! Awesome. It works great. I presume the routine must reside in a function/macro library since the helper function is necessary and thus routines that call the macro must be able to access the helper function or else the code must be included in any routine that uses the macro. How do you like to handle this? I run a lot of batch routines with 20+ computers and have most of the routines on a mapped utility drive on the server. It seems prudent to include only the extra library code that a particular routine needs to search for rather than the whole library being available to every routine? Then one obviously wishes to have each subroutine residing in only one place when modifications may become necessary. I'm just curious how you handle these issues in real life? TIA.

One more question... How to modify your routine so that multiple variables could be returned?

Re: return over an endlocal barrier with a macro

Posted: 08 Aug 2013 06:14
by dbenham
:D I like it :!:

I suppose your non-delayed return with the hidden function call is marginally slower than my original design with the two phased macro call, but your design is much simpler to incorporate into functions; definitely worth the trade-off.

Along the same lines as what carlsomo was saying, I suggest putting the macro definition and the helper function in a stand-alone script, call it jebReturn.bat ( or safeReturn.bat :wink: )

I would structure the script something like this:

Code: Select all

@echo off
if not defined jebReturn goto :initJebReturn
if "%~1" equ "" exit /b

:: __disDelayedReturn helper function goes here. No need for a label

exit /b

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

set "jebDisabledReturn=%~f0"

set ^"jebReturn= ....
...
             call %jebDisabledReturn %%~N%\n%
...
) else set args="

exit /b

It is important that the script not have a SETLOCAL at the top.

The safe Return technique is now wholly self contained. Simply put the script somewhere within the PATH definition, and it becomes trivial to incorporate into any function in any batch script.

someBatchScript.bat

Code: Select all

@echo off
setlocal disableDelayedExpansion

:: initialize safe return
call jebReturn
:: or IF NOT DEFINED %jebReturn% CALL jebReturn
:: but the IF is not required since jebReturn will return immediately if called without args
:: and jebReturn variable is already defined

::now use it in function definition
:someFunction  RtnVar
setlocal  %= delayed expansion enabled or disabled =%
set rtn=value
%jebReturn% rtn %1 1
exit /b


Yes, I really like your new design jeb :D

Now the last thing required is a jebReturnMulti for multiple return values. My original script has some techniques that should be useful.


Dave Benham

Re: return over an endlocal barrier with a macro

Posted: 08 Aug 2013 06:59
by jeb
dbenham wrote:Along the same lines as what carlsomo was saying, I suggest putting the macro definition and the helper function in a stand-alone script, call it jebReturn.bat ( or safeReturn.bat :wink: )

Yes, your idea to call the helper function in the safeReturn.bat will solve the problems. :)

But my goal is to solve it completely with a macro, but currently I don't see a solution.

Your solution uses two macro expansions to solve the main problem to tranfer over the endlocal barrier.
When the expansion mode after the endlocals is still enabled, I found a solution, but not for the dsiabled part.

Also the mutli variable return seems to be much more complicated with my single macro desgin.
But this could be solved when I always call the helper function. :idea:
But as I said, I want to avoid the helper function at all. :(

And even using your two macros approach wouldn't solve it inside of blocks, as there I can't see a way to use the two macro technic.
As the second macro is useless then.

Code: Select all

for ... (
  %prepareReturn% returnVar transferVar
  %safeReturn%
)



jeb

Re: return over an endlocal barrier with a macro

Posted: 09 Aug 2013 00:33
by carlsomo
jeb, if anyone can solve it, you can. I think solving the multiple variable option is more important than the macro only attempt, heroic as it may be. I am just amazed by what can be done with this archaic programming language with the very limited tools available. But the beauty is portability among all the Windows modern platforms. Anyone who says that it is not programming or problem solving at a very advanced level of thinking is sadly mistaken and missing out on very cool solutions to problems that 99.9999% of the population don't even know exist. I am humbled often by this website and the contributions put forward. As a scientist I appreciate the science and the art involved with these contributions and I apply what I have learned from this website in a real world environment almost daily. The important fellows who post here are truly advanced individuals and should be recognized as such. Hats off to you all.

Carl

Re: return over an endlocal barrier with a macro

Posted: 01 Dec 2013 14:12
by dbenham
I'm not sure why, but somehow this thread is continued under a new Post: ReturnVar macro revisited

Dave Benham