Page 1 of 4

Rules for label names vs GOTO and CALL

Posted: 25 Sep 2012 06:02
by jeb
Hi,

I will try to discover a bit the rules for legal label names and how they work.

Normally you choose a name containing numbers and characters.
But you could also use any character with different effects.

Code: Select all

set "amp=^&"
set "pipe=^|"
set "lt=^<"
set "gt=^>"
call :%%%%this%%%%"label"^looks:%%lt%%a%%pipe%%bit%%gt%%^^odd
exit /b

:%this%"label"^looks<quite|a> ^^:+bit&normally
echo The function is called via "%0"
exit /b

Output wrote:The function is called via ":%this%"label"looks:<a|bit>^odd"

It seems that the label does not fit to the calling name, but both labels and call-names have different rules.

A call-name (the destination label behind a CALL) is parsed normally and stored in %0 by the normal argument rules.
But the destination label can be shorter than %0, as there exists some STOP characters.

Code: Select all

+:,;=  <space>,<TAB> and <LF>

These characters stop the call-name, independent if they are escaped or quoted.

There exists some more characters which can be used, but they are a bit more tricky to handle, like

Code: Select all

|&<>(

They can be used but only with late expansion in the call.
Like

Code: Select all

set "amp=^&"
call :Test%%amp%%yes
exit /b

:test^&yes
echo Called by "%0"
exit /b


At this point it's necessary to understand, how the CALL build the name. As each CALL parses a line two times, you need to escape each special character two times.
And as you can't escape it directly two times with carets, I used here late percent expansion.
You could also use the late expansion only for the caret, like

Code: Select all

set "caret=^"
call :test%%caret%%^&yes
exit /b

:test^&yes
echo Called by "%0"
exit /b


GOTO is similar to CALL, but as it uses only one parse phase, it's easier to create the GOTO-names.

Now the label names.
They also use STOP characters, but not the same, I only know two :!:

Code: Select all

:+

So these labels are equivalent

Code: Select all

:one
:one:two
:one+two
:one^


Btw. label names have completly different parser phases, only a bit escaping is done,
but the percent expansion nor the remove phase for CR's are executed.
A caret escape the next character, even in quotes, but it can't be used as multiline character (for the label name).

And a label can be found even if it does not begin with a colon :!:
These labels are all equivalent

Code: Select all

:label
=,,;;  ,;= :label
X =,,;;  ,;= :label
< :label

The rule is here:
<Anyone character or none> followed by any ,;=<space> then the colon and the name.

This can sometimes be handy.

Code: Select all

@echo off
cls
call :#;#
%:#+~%,=*
>=,;=;,,=,;:%%+,=;echo\=%01>&2&call=;:%%%%%%%%;%*
echo *%*


hope it helps
jeb

Re: Rules for label names vs GOTO and CALL

Posted: 25 Sep 2012 08:47
by Ed Dyreen
'
Cats n keyboards :roll:

Code: Select all

@echo off
cls
call :#;#

:#
echo.label=%0_
echo. args=%*_

> :%% echo. &call ;:%%%%%%%%;%*
echo.label=%0_
echo. args=%*_

pause
exit
Nice one, it occupied me 30mins :D LOL

Re: Rules for label names vs GOTO and CALL

Posted: 25 Sep 2012 14:51
by Liviu
Holy smoke ;-) ...err, I mean, really neat find!

Following tried on XP sp3.

Code: Select all

@echo off

call :tee 1 2 3
call ::tee 1 2 3
call :::tee 1 2 3
call :tee:::::::::::::: 1 2 3

if exist "%~dp0logfile.txt" del "%~dp0logfile.txt"
call :tee+"%~dp0logfile.txt" 1 2 3
call :tee+"%~dp0logfile.txt" 1 2 3 4 5 6 7 8 9 10 11 12 13
goto :eof

:tee
echo %0 ^( %* ^)
setlocal & set tee=%0
if not %tee::=%==tee (
  >>%tee:*+=% echo %0 ^( %* ^)
)
endlocal
Output.

Code: Select all

C:\tmp>calltest
:tee ( 1 2 3 )
::tee ( 1 2 3 )
The system cannot find the batch label specified -
:tee:::::::::::::: ( 1 2 3 )
:tee+"C:\tmp\logfile.txt" ( 1 2 3 )
:tee+"C:\tmp\logfile.txt" ( 1 2 3 4 5 6 7 8 9 10 11 12 13 )

C:\tmp>type logfile.txt
:tee+"C:\tmp\logfile.txt" ( 1 2 3 )
:tee+"C:\tmp\logfile.txt" ( 1 2 3 4 5 6 7 8 9 10 11 12 13 )

One point is that parsing is indeed strange - "call :tee" and "call ::tee" work, but "call :::tee" appears to resolve to an empty label and fails. Trailing : colons don't seem to have any limit.

The other point is that it gives a way to pass additional arguments to a call'd label, outside the regular %1, %2 etc list. This might be useful in cases where the called subroutine needs to work on the entire %* line, as opposed to individual arguments. For a quick-and-dirty example, the :tee above takes an optional filename appended to its %0 label, and appends a copy of the standard output to that file. The classical way, this would require significantly more complicated code to (a) identify the filename among other parameters, and (b) shift the rest of the parameters and manually rebuild a "shifted" %*.

Liviu

Re: Rules for label names vs GOTO and CALL

Posted: 25 Sep 2012 15:50
by jeb
Liviu wrote:One point is that parsing is indeed strange - "call :tee" and "call ::tee" work, but "call :::tee" appears to resolve to an empty label and fails.
Interessting, I never thought about to test it :o
But GOTO does only accept one or zero colons in front :!:

And another differenece between CALL and GOTO, a CALL to an undefined label prints an error message and continues.
A GOTO prints the same error message, but then it works like a GOTO :EOF, it works like an RETURN.

Liviu wrote:Trailing : colons don't seem to have any limit.
No, as the first colon stops the called label name.

I played a bit with some extreme called label names, like embedding a linefeed or carriage return.
A carriage return can be a valid label name, but I can only access it via a GOTO, as I'm not able to create a CALL without removing my CR.

Code: Select all

@echo off
setlocal EnableDelayedExpansion
for /f %%a in ('copy /Z "%~dpf0" nul') do set "CR=%%a"
goto :1!CR!x
echo Never reached
:1<CR>x
echo It works

I replaced the text <CR> in : 1<CR>x with a good editor to a single CR-character!

Or a line feed

Code: Select all

@echo off
setlocal EnableDelayedExpansion
set lf=^


set "xlf=^^!lf!!lf!"
call :AAA:^^%%XLF%%%%XLF%%BBB
exit /b

:AAA
echo CALLED by #%0#
exit /b


Output wrote:CALLED by #:AAA:
BBB#


jeb

Re: Rules for label names vs GOTO and CALL

Posted: 25 Sep 2012 19:41
by dbenham
:shock: Curioser and curioser. Well done jeb (and Liviu)

So a line consisting of a path (including drive letter) to a batch (or other executable) can function as both a label and an instantiation of the batch.

Assuming my batch script is TEST.BAT residing somewhere on the C: drive

Code: Select all

@echo off
setlocal
pushd "%~dp0"

if "%~1"=="stop" (
  echo stopping
  exit /b
)

call :test
echo before label
C:test stop
echo after label

results

Code: Select all

after label
before label
stopping


Dave Benham

Re: Rules for label names vs GOTO and CALL

Posted: 25 Sep 2012 21:47
by Liviu
dbenham wrote:So a line consisting of a path (including drive letter) to a batch (or other executable) can function as both a label and an instantiation of the batch.

Nice! FWIW the same happens with partially quoted names even when continuations past the closing quote don't match. For example, saving the following as "t e s t.cmd"

Code: Select all

@echo off
setlocal
pushd "%~dp0"

if "%~1"=="stop" (
  echo stopping = %0 ^( %* ^)
  exit /b
)

call :"t e s t" 123
echo before label = %0 ^( %* ^)
C:"t e s t" stop
echo after label = %0 ^( %* ^)
gives

Code: Select all

C:\tmp>"t e s t" 987
after label = :"t e s t" ( 123 )
before label = "t e s t" ( 987 )
stopping = C:"t e s t" ( stop )

Liviu

P.S. This is all jeb's doing, none of my fault ;-)

Re: Rules for label names vs GOTO and CALL

Posted: 25 Sep 2012 22:20
by foxidrive
If anyone actually uses that in a batch file to help someone, I will have to take drastic action. *polishes Uzi*


Bloody crackpot batch users. ;)

Re: Rules for label names vs GOTO and CALL

Posted: 26 Sep 2012 02:57
by jeb
foxidrive wrote:If anyone actually uses that in a batch file to help someone, I will have to take drastic action. *polishes Uzi*

I use it :)

Code: Select all

call :myFunc
echo back again

:myFunc ^
echo This is function %0 & exit /b
echo ERROR: You don't call the function
exit /b


Also my batch library uses a simple trick

Code: Select all

REM 1. Prepare the BatchLibrary for the start command
call BatchLib.bat

REM 2. Start of the Batchlib, acquisition of the command line parameters, swtich to the temporary LibTest1.BLibTmp.bat
<:%BL.Start%


BL.Start contains wrote:BL.Start=:< nul ( setlocal EnableDelayedExpansion & set "bl.cmdcmdline=!cmdcmdline!" & call set "bl.param[0]=%~f0" & FOR /L %n IN (1,1,9) DO ( call set "bl.para
m[%n]=%~1" & shift /1 ) ) & "D:\Projekte\EHS\Batch\BatchLib\BatchLib.bat" /INTERN_START "!bl.param[0]!"

Or formatted

Code: Select all

BL.Start=:< nul ( 
  setlocal EnableDelayedExpansion
  set "bl.cmdcmdline=!cmdcmdline!"
  call set "bl.param[0]=%~f0"
  FOR /L %n IN (1,1,9) DO (
    call set "bl.param[%n]=%~1"
    shift /1
  )
)     
"D:\Projekte\EHS\Batch\BatchLib\BatchLib.bat" /INTERN_START "!bl.param[0]!"


The trick is here that the first time

Code: Select all

<:%BL.Start%

is expanded to an executable statement.
And later it can be used as a label :)

jeb

Re: Rules for label names vs GOTO and CALL

Posted: 26 Sep 2012 05:03
by Ed Dyreen

Re: Rules for label names vs GOTO and CALL

Posted: 26 Sep 2012 11:39
by Liviu
jeb wrote:The rule is here:
<Anyone character or none> followed by any ,;=<space> then the colon and the name.

Another oddity is that parsing appears to be based on the "physical" text lines in the source file, and recognizes a label even on a line which is in fact a continuation of the previous one i.e. when the line above it ends with a ^ caret.

Code: Select all

@echo off
setlocal
pushd "%~dp0"
echo wscript.echo "***  :ads.vbs ( " ^& wscript.arguments(0) ^& " ) ***" >test2.cmd:ads.vbs
call :ads.vbs 123
echo before label = %0 ^( %* ^)
cscript //nologo test2.cmd^
:ads.vbs 456
echo after label = %0 ^( %* ^)
This code saved as test2.cmd on an NTFS drive and run on XP sp3 outputs...

Code: Select all

C:\tmp>test2 987
after label = :ads.vbs ( 123 )
before label = test2 ( 987 )
***  :ads.vbs ( 456 ) ***
after label = test2 ( 987 )

Note that using the vbscript code embedded as an NTFS ADS (alternate data stream) is just for fun, and to give foxidrive more ammunition of course ;-) but is not essential to the main point, which is that the :ads.vbs label is recognized in the middle of a split line.

Liviu

Re: Rules for label names vs GOTO and CALL

Posted: 26 Sep 2012 11:49
by Squashman
foxidrive wrote:If anyone actually uses that in a batch file to help someone, I will have to take drastic action. *polishes Uzi*


Bloody crackpot batch users. ;)

I agree and for our U.S. users I have been polishing mine in case I meet Roger Goodell in a dark alley!!!!! :evil:

Re: Rules for label names vs GOTO and CALL

Posted: 16 Nov 2012 02:44
by jeb
There exists an interesting additional parser phase.

Normally delayed expansion is evaluated only once, even in a call or call call statement.

Code: Select all

@echo off
set "perVar=%%perVar%% percent-expansion,"
set "delVar=!delVar! delayed-expansion,"
echo %perVar%
call echo %perVar%
call call echo %perVar%
setlocal EnableDelayedExpansion
echo !delVar!
call echo !delVar!
call call echo !delVar!

output wrote:%perVar% percent-expansion,
%perVar% percent-expansion, percent-expansion,
%perVar% percent-expansion, percent-expansion, percent-expansion,
!delVar! delayed-expansion,
!delVar! delayed-expansion,
!delVar! delayed-expansion,


That's not new and the standard behaviour of the parser, but now ... :shock:

Code: Select all

setlocal EnableDelayedExpansion
set "var=myLabel"
set "pVar=^!var^!"
echo !pVar!
call echo !pVar!
call :!pVar!
exit /b

:myLabel
setlocal DisableDelayedExpansion
echo myLabel is called, the func name is %0
exit /b

Output wrote:!var!
!var!
myLabel is called, the func name is :!var!


Obviously !var! is also expanded, but only when call to find a label, not if it only echo something.
And %0 doesn't contain the correct label name then.

jeb

Re: Rules for label names vs GOTO and CALL

Posted: 16 Nov 2012 07:27
by dbenham
:shock: Wow jeb, that is amazing.

The extra round of delayed expansion only works with CALL, it does not work with GOTO. I guess that is "expected".

Liviu's discovery about physical parsing of lines is also very interesting. Label lines support line continuation during normal parsing, but do not continue if discovered by CALL or GOTO.

This example is fun to trace :wink:

Code: Select all

@echo off
setlocal enableDelayedExpansion

set "var=myLabel"
set "dvar=^!var^!"
echo before CALL
call :!dvar!
echo after CALL

:ContinuedLabel with embedded commands that only execute if this label is called ^
echo After ContinuedLabel & exit /b

echo This continued line has an embedded label { ^
:myLabel } followed by an embedded ^
goto continue that only works if :myLabel is called & exit /b
goto !dvar!

:continue
setlocal disableDelayedExpansion
echo Inside call to %0
endlocal
goto :ContinuedLabel

output

Code: Select all

before CALL
Inside call to :!var!
After ContinuedLabel
after CALL
This continued line has an embedded label { :myLabel } followed by an embedded goto continue that only works if :myLabel is called


Dave Benham

Re: Rules for label names vs GOTO and CALL

Posted: 19 Nov 2012 03:20
by jeb
I retested a bit and have to correct myself.

CR and LF seems to be always stop characters, (my samples were to simple).

jeb wrote:Btw. label names have completly different parser phases, only a bit escaping is done,
but the percent expansion nor the remove phase for CR's are executed.
A caret escape the next character, even in quotes, but it can't be used as multiline character (for the label name).

There seems to be one open possibility. A caret do some sort of multiline, but I can't figure out how the label is called then.

Code: Select all

@echo off
set pVar=!var!
set LF=^


setlocal EnableDelayedExpansion
call :lab    test1
call :label  test2
set "var=lab^el"
call :!pVar! test3
set "var=lab^^el"
call :!pVar! test4
set "var=lab!LF!el"
call :!pVar! test5
set "var=lab^^!LF!el"
call :!pVar! test6
exit /b

:lab^
el
echo Currently I can't call this


Btw. Do ALWAYS append a parameter to your call, if experimenting with ending carets, else the cmd.exe can crash completly :!:

EDIT:
dbenham wrote:The extra round of delayed expansion only works with CALL, it does not work with GOTO. I guess that is "expected".

Yes, I expected this, as GOTO haven't a second parser round like CALL at all.

CALL seems to have a special mode for calling labels, as the second delayed expansion only works if a colon is found before,
it fails if the colon would only be visible after the second expansion.

Code: Select all

set "pVar=!var!"
setlocal EnableDelayedExpansion
set "var=label"
call :!pVar! works
set "var=:Label"
call !pVar! failed
exit /b

:label
echo It works
exit /b

jeb

Re: Rules for label names vs GOTO and CALL

Posted: 25 Jun 2014 14:25
by npocmaka_
I'm not sure if this is exactly applicable for this thread but seems that for bot GOTO and CALL the
string "/?" is with highest priority and both commands ignore everything else when this is string is passed and print the help:

Code: Select all

@echo off
call :blah/?blah
goto :blah/?blah

call :label1 /?
goto :label1 /?

exit /b 0

:label1
echo ---------------