Skip to content
This repository has been archived by the owner on Feb 22, 2022. It is now read-only.

endlocal extension #46

Open
carlos-montiers opened this issue Jan 17, 2020 · 13 comments
Open

endlocal extension #46

carlos-montiers opened this issue Jan 17, 2020 · 13 comments

Comments

@carlos-montiers
Copy link
Owner

carlos-montiers commented Jan 17, 2020

Currently we have extensions for change directly the delayedexpansion and extensions without create a new local context, useful for some quickly switch of that options, nothing related to create local contexts.
I think will be useful have an extension for do endlocal if somene wants create a local context. but this endlocal extension will do endlocal without restore options like delayexpansions.

For write variables in the parent context a technique like this can be used:

:label
setlocal
set lvar=a
(
endlocal
set parentvar=%lvar%
)
goto :eof

Note: Seems that in a subroutine context goto :eof or exit /b executes and implicit endlocal only if there exists a setlocal not ended inside the subroutine.

The main issue with the previous technique is that endlocal restores delayedexpansion, causing this issue:

set x=1
call :a
set x
pause
goto :eof

:a
setlocal enabledelayedexpansion
set z=3
(
endlocal
set x=!z!
)
goto :eof

It prints:x=!z!

Thus I think that maybe an ENDLOCAL extension should be useful. This only end the current context, nothing more, that is the only one responsability, and are like the extensions DELAYEDEXPANSION and EXTENSIONS that also are single responsability.

@carlos-montiers carlos-montiers changed the title setlocal endlocal endlocal extension Jan 17, 2020
@DaveBenham
Copy link
Collaborator

DaveBenham commented Jan 17, 2020

Actually, the best way to transfer an environment variable across the ENDLOCAL barrier is to use a global heap variable!

I would structure the return as follows (the complexity is to guarantee safe return of any value, and no side effects)

:label
setlocal enableDelayedExpansion
set lvar=xxx
....
set "$returnTemp=!lvar!"
2>nul (goto) (
  if "!!"=="" (set "parentVar=!$returnTemp!") else (
    set @delayedexpansion=1
    set "parentVar=!$returnTemp!
    set @delayedexpansion=0
  )
  set "$returnTemp="
)

But that is more code than I want to write.

I would like a @return extension that does basically what I have laid out above.

But I would have it take pairs of arguments, plus an optional return code, as in

call @return localVar1 parentVar1 [localVarN parentVarN]... [returnCode]

@carlos-montiers
Copy link
Owner Author

carlos-montiers commented Jan 17, 2020

Possible duplicate on: #32 (comment)
But in this issue ENDLOCAL extension is proposed with single responsability.

@carlos-montiers
Copy link
Owner Author

carlos-montiers commented Jan 17, 2020

Actually, the best way to transfer an environment variable across the ENDLOCAL barrier is to use a global heap variable!
But that is more code than I want to write.
I would like a @return extension that does basically what I have laid out above.
But I would have it take pairs of arguments, plus an optional return code, as in

call @return localVar1 parentVar1 [localVarN parentVarN]... [returnCode]

Copy heap global variable to environment block for allow return values I thinks is not good.
The return extension is interesting, but I like more a version named outvar

call @outvar parentVar1 localVar1
exit /b returnCode

That is a litte bit better than the proposed single responsability endlocal extension, because is not needed write parenthesis for have the value after the endlocal, copied in the parenthesis block, and now that I'm think it again, is good that the endlocal restore all, else you will get in the parent context with possible changed delayed expansion option.

@adoxa Jason. I think this can be implemented as this:
when the extension outvar is called create an array of pair value,
because we have already hooked goto :eof, exit /b I understand it internally calls to goto :eof, thus in the goto :eof hook, looks for this array and call to MySetEnvironmentVariable.
The only catch that I see is that goto: eof not works or almost is not found with extensions disabled. Is feasible do this ?

@adoxa
Copy link
Collaborator

adoxa commented Jan 17, 2020

Goto :eof won't work, as that occurs before EndLocal. Having calls automatically create local heap variables and providing a syntax to get/set global/parent heap variables is far easier for me to implement. Perhaps something like:

:sub
:: declare global variables
call @global $global
:: declare parent variables
call @caller $return
:: everything else is local to :sub

@carlos-montiers
Copy link
Owner Author

carlos-montiers commented Jan 17, 2020

Good idea Jason, but I have doubt about the concept of declaration, because in batch always is needed set a value. Thus, is missed the value?. About GLOBAL I think is more simple continue with the global heap variable prefixed with $. About CALLER, what about name it INVSET as brief of invoker set, this can use the same syntax of set. Other option I think can be better is create a specialized extension SET where you can indicate options, for example the concept of type.

:: set variable in the invoker context
call @SET /INV a=b
or 
call @SET /INV a b
:: set a variable reading a file
call @SET /F content=file
or
call @SET /F content file
:: set a variable with the result of a command
call @SET /C output=command
or
call @SET /C output command

@adoxa
Copy link
Collaborator

adoxa commented Jan 17, 2020

The declaration is for the scope of the variable, not its value. Maybe OUT would be better than CALLER.

set $global=global
set $local=local
set $return=
call :sub
echo %$global% %-- this is global%
echo %$local% %-- local%
echo %$return% %-- this is what called sub%
goto :eof
:sub
call @global $global
call @out $return
set $global=this is global
set $local=this is local to sub
set $return=this is what called sub

@carlos-montiers
Copy link
Owner Author

Jason calling a label not create a local scope:

@echo off
setlocal enabledelayedexpansion

set a=ww
set b=xx
call :lbl
echo %a%
echo %b%
:: zz
:: xx
goto :eof

:lbl
set a=zz
goto :eof

Thus I think in your last example idea, we never have working with a local scope.
But I understand the idea, what about scopeup:

@echo off
setlocal enabledelayedexpansion

set a=ww
set b=xx
call :lbl
echo %a%
echo %b%
:: nn
:: xx
goto :eof

:lbl
setlocal
set a=nn
call @scopeup a
goto :eof

With that, we can easily bypass the endlocal barrier, and we keeps working with the global heap variables prefixed with $.

@carlos-montiers
Copy link
Owner Author

Maybe instead of scopeup name it upsc

@DaveBenham
Copy link
Collaborator

DaveBenham commented Jan 19, 2020

I don't recall ever seeing any other language that had a functionality like scopeup that returned variables to a parent scope without also returning from some routine. I am not a fan.

To implement an @return as I laid out ought to be possible. It would need to store the return variables in temporary heap variables and stash a callback routine (or set of instructions) somewhere in memory, and then execute EXIT /B (or GOTO EOF).

The EXIT /B / GOTO EOF would need to be patched to look and execute any @return callback if it happens to be there. The callback would then transfer the temp heap values to the final destination in the parent scope.

@adoxa
Copy link
Collaborator

adoxa commented Jan 19, 2020

Jason calling a label not create a local scope:

No surprise, as I haven't written it yet. It will only apply to heap variables, if you want local environment variables continue to use SetLocal.

But I understand the idea, what about scopeup:

That creates a local variable and a parent variable and I'm not going to support environment variables.

With that, we can easily bypass the endlocal barrier, and we keeps working with the global heap variables prefixed with $.

That is not an easy way to bypass the local barrier. And just because "it ought to be possible" doesn't mean it's feasible. I'll make it simple: you'll get what you're given. :) ATM that's making heap variables automatically local to calls, with functions to declare global or "out" heap variables; there will be no environment variable support.

FYI, exit /b is implemented by setting errorlevel and executing goto :eof; goto :eof just sets the file pointer to the end of the file.

@carlos-montiers
Copy link
Owner Author

carlos-montiers commented Jan 21, 2020

Mmm, I'm thinking in a new approach, create a new special type of heap variable, that will allow reassign it internally with pointer assign for save the overhead of copy it:

@echo off

call :lbl
set $r= @return
:: $r= xd
goto :eof

:lbl
setlocal
set @returns=xd
endlocal
goto :eof

In this case the RETURNS set the value of a heap variable that only be reassigned one time, thus outside the label, when RETURN is assigned (note: without s), it will point in this case $r to the pointer of RETURN and RETURN now points to NULL.

With that we can use a heap variable for bypass the endlocal barrier, and save the overhead of recopy it.

For return two values, we can wait until the support for array variables be done, thus we can reassign also two values with pointer assignment.

@adoxa
Copy link
Collaborator

adoxa commented Jan 21, 2020

I don't see how that's any different to my approach, which also allows multiple variables.

@echo off

call :lbl
:: $r = xd
goto :eof

:lbl
call @out $r
set $r=xd
goto :eof

Your approach does have the advantage of knowing exactly what is returned, though, without needing to refer to the subroutine. Still no support for globals (I really want heap variables automatically local). Another approach is to use a parameter, but you'd also need to refer to the subroutine to know that it requires a return argument (not really a big deal, that's what other languages do, after all).

call :lbl @out $r
:: $r = xd
goto :eof

:lbl
set %1=xd
goto :eof

No, that's not right, it should be with the label (or maybe even both).

call :lbl $r
:: $r = xd
goto :eof

:lbl @out %1
set %1=xd
goto :eof

That could be tricky to implement, and not sure I like it, anyway. @Return (I think that can serve for both, no need for @returns too) might actually be the way to go. Apart from globals.

@carlos-montiers
Copy link
Owner Author

I vote up for the @return approach. I think is practical and easy to use.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants