infinite loop with break condition

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
dbenham
Expert
Posts: 1961
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: infinite loop with break condition

#16 Post by dbenham » 29 Dec 2011 09:44

aGerman wrote:It seems the cmd is parsing the syntax of IF even if it is not executed yet.
Is there a way to avoid that "early parsing" by any kind of escape sequence?

The IF statement MUST be parsed prior to execution. The comparison operator is needed at parse time, so delayed expansion cannot be used. The same is true for the IF /I option, and also for FOR options.

I can think of 2 solutions.

1) Dynamically build the macro prior to each execution, substituting the actual operator in the definition.

2) Anticipate all possible operators with considerably more code.

Code: Select all

...
  if /i !_c2! == lss (%\n%
    if not !%%K! lss !_c3! exit !%%L!%\n%
  ) else if /i !_c2! == leq (%\n%
    if not !%%K! leq !_c3! exit !%%L!%\n%
  ) else etc.
...


Dave Benham

aGerman
Expert
Posts: 3063
Joined: 22 Jan 2010 18:01
Location: Germany

Re: infinite loop with break condition

#17 Post by aGerman » 29 Dec 2011 10:18

Thanks Dave. Now I understand that behaviour.

Code: Select all

@echo off &setlocal DisableDelayedExpansion

%$initWhile% :test
call :macros

cmd /c "%~f0" test num lss 50 ret num 5 ret 6
echo Macro returned %errorlevel%
pause
goto :eof

:::::::::::::::::::::::::::::::::::::::::::::::::

:test
%$While%
  set /a num+=1
  set /a ret+=num
  echo !num! !ret!
%$Wend%

:::::::::::::::::::::::::::::::::::::::::::::::::

:macros
set LF=^


set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"&rem TWO EMPTY LINES ABOVE REQUIRED!

set $initWhile=for /l %%I in (1 1 2) do if %%I==2 (%\n%
  if defined _arg (%\n%
    for /f %%J in ("!_arg!") do endlocal^&call %%J %%*%\n%
  ) else exit%\n%
) else setlocal EnableDelayedExpansion ^&set _arg=

set $While=(%\n%
  setlocal EnableDelayedExpansion%\n%
  call set "_args=%%*"%\n%
  if "!_args!"=="" exit 1%\n%
  for %%J in (!_args:* ^^=!) do if not defined _c1 (set "_c1=%%J") else (%\n%
    if not defined _c2 (set "_c2=%%J") else (%\n%
      if not defined _c3 (set "_c3=%%J") else (%\n%
        if not defined _ret (set "_ret=%%J") else (%\n%
          if not defined _var (set "_var=%%J") else (set "!_var!=%%J"^&set "_var=")%\n%
  ))))%\n%
  for /l %%J in (0) do (%\n%
    for /f "tokens=1,2" %%K in ("!_c1! !_ret!") do (%\n%
      if /i !_c2!==equ if not !%%K! equ !_c3! exit !%%L!%\n%
      if /i !_c2!==leq if not !%%K! leq !_c3! exit !%%L!%\n%
      if /i !_c2!==lss if not !%%K! lss !_c3! exit !%%L!%\n%
      if /i !_c2!==geq if not !%%K! geq !_c3! exit !%%L!%\n%
      if /i !_c2!==gtr if not !%%K! gtr !_c3! exit !%%L!%\n%
      if /i !_c2!==neq if not !%%K! neq !_c3! exit !%%L!%\n%
    )%\n%

 
set $Wend=))

goto :eof

Regards
aGerman

Aacini
Expert
Posts: 1465
Joined: 06 Dec 2011 22:15
Location: México City, México

Re: infinite loop with break condition

#18 Post by Aacini » 29 Dec 2011 12:08

Thanks a lot Dave, it works now!

Code: Select all

set while=for %%n in (1 2) do if %%n==2 (%\n%
      call :StrLen argv argvLen=%\n%
      set "body=!argv:*do=!"%\n%
      call :StrLen body bodyLen=%\n%
      set /A condLen=argvLen-bodyLen-2%\n%
      for %%a in (!condLen!) do set "cond=!argv:~0,%%a!"%\n%
      echo for /L %%%%w in (1,0,1^^) do if !cond! !body! else exit ^^^^!whileResult^^^^!^> whileBody.bat%\n%
      cmd /V:ON /Q /C whileBody%\n%
      endlocal ^& set whileResult=^!errorlevel^!%\n%
   ) else setlocal EnableDelayedExpansion ^& set argv=
EDIT: I added /V:ON switch to avoid an error described below.

WHILE macro is used this way:

Code: Select all

%WHILE% condition DO ( commands )

The condition is exactly the same of IF command.
To include multiple commands, terminate each line with %\n% (macro style). EDIT: Parentheses are mandatory.
To get a variable value in the condition or while body, enclose its name between %!%; this method is easier to remember than multiple ^^^^... and looks balanced (and I like it!) :)
"whileResult" variable preserve its (numeric) value after the while ends.
Some examples:

Code: Select all

:factorial N R=
setlocal
set num=1
set whileResult=1
%while% %!%num%!% leq %1 do ( set /A whileResult*=num, num+=1 )
endlocal & if "%2" neq "" (set %2=%whileResult%) else echo %whileResult%
exit /B

:lcm n1 n2 lcm=
setlocal
set /a "whileResult=%1, j=%2"
%while% %!%j%!% neq 0 do ( set /a k=j, j=whileResult%%%%j, whileResult=k )
set /a j=%1*%2/whileResult
endlocal & if "%3" neq "" (set %3=%j%) else echo %j%
exit /B


call :factorial 10 factor=
echo Factorial of 10: %factor%
echo/

call :lcm 3527 3784 var=
echo Least common multiple of 3527 and 3784: %var%
echo/

echo Calculation of e:
set /A digits=6, one=1
for /L %%i in (1,1,%digits%) do set one=!one!0
set /A num=0, fact=1, factXone=fact*one, whileResult=0
echo #-   #%!%    term   summation
%while% %!%factXone%!% gtr 0 do ( %\n%
   set /A term=one/fact, whileResult+=term %\n%
   echo %!%num%!%-   %!%fact%!%   %!%term%!%   %!%whileResult%!% %\n%
   set /A num+=1, fact*=num, factXone=fact*one %\n%
)
echo Number e = !whileResult:~0,-%DIGITS%!.!whileResult:~-%DIGITS%!
echo/

Results:

Code: Select all

Factorial of 10: 3628800

Least common multiple of 3527 and 3784: 13346168

Calculation of e:
#-   #!    term   summation
0-   1   1000000   1000000
1-   1   1000000   2000000
2-   2   500000   2500000
3-   6   166666   2666666
4-   24   41666   2708332
5-   120   8333   2716665
6-   720   1388   2718053
7-   5040   198   2718251
8-   40320   24   2718275
9-   362880   2   2718277
Number e = 2.718277
Last edited by Aacini on 14 Jan 2012 11:40, edited 2 times in total.

aGerman
Expert
Posts: 3063
Joined: 22 Jan 2010 18:01
Location: Germany

Re: infinite loop with break condition

#19 Post by aGerman » 29 Dec 2011 15:57

Hi Aacini.

I like your syntax more than mine but I dont get it run. Have a look at what I thought you did to create the output. What's wrong?

Code: Select all

@echo off
setlocal DisableDelayedExpansion
set LF=^


::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

set while=for %%n in (1 2) do if %%n==2 (%\n%
      call :StrLen argv argvLen=%\n%
      set "body=!argv:*do=!"%\n%
      call :StrLen body bodyLen=%\n%
      set /A condLen=argvLen-bodyLen-2%\n%
      for %%a in (!condLen!) do set "cond=!argv:~0,%%a!"%\n%
      echo for /L %%%%w in (1,0,1^^) do if !cond! !body! else exit ^^^^!whileResult^^^^!^> whileBody.bat%\n%
      cmd /Q /C whileBody%\n%
      endlocal ^& set whileResult=^!errorlevel^!%\n%
   ) else setlocal EnableDelayedExpansion ^& set argv=

set !=^^^^!


setlocal EnableDelayedExpansion

call :factorial 10 factor=
echo Factorial of 10: %factor%
echo/

call :lcm 3527 3784 var=
echo Least common multiple of 3527 and 3784: %var%
echo/

echo Calculation of e:
set /A digits=6, one=1
for /L %%i in (1,1,%digits%) do set one=!one!0
set /A num=0, fact=1, factXone=fact*one, whileResult=0
echo #-   #%!%    term   summation
%while% %!%factXone%!% gtr 0 do ( %\n%
   set /A term=one/fact, whileResult+=term %\n%
   echo %!%num%!%-   %!%fact%!%   %!%term%!%   %!%whileResult%!% %\n%
   set /A num+=1, fact*=num, factXone=fact*one %\n%
)
echo Number e = !whileResult:~0,-%DIGITS%!.!whileResult:~-%DIGITS%!
echo/
pause
goto :eof


:factorial N R=
setlocal
set num=1
set whileResult=1
%while% %!%num%!% leq %1 do ( set /A whileResult*=num, num+=1 )
endlocal & if "%2" neq "" (set %2=%whileResult%) else echo %whileResult%
exit /B

:lcm n1 n2 lcm=
setlocal
set /a "whileResult=%1, j=%2"
%while% %!%j%!% neq 0 do ( set /a k=j, j=whileResult%%%%j, whileResult=k )
set /a j=%1*%2/whileResult
endlocal & if "%3" neq "" (set %3=%j%) else echo %j%
exit /B

:StrLen string [result=[adjust]]
setlocal EnableDelayedExpansion
set str=%1
set str=!str:"= !
if "!str:~0,1!" == " " (
    set "str=0%~1"
) else (
    set "str=0!%1!"
)
set len=0
for /L %%A in (12,-1,0) do (
   set /A "len|=1<<%%A"
   for %%B in (!len!) do if "!str:~%%B,1!" == "" set /A "len&=~1<<%%A"
)
for %%v in (!len!) do endlocal&if not "%2" == "" (set /A "%2=%%v%3") else echo %%v
exit /B


Regards
aGerman

Aacini
Expert
Posts: 1465
Joined: 06 Dec 2011 22:15
Location: México City, México

Re: infinite loop with break condition

#20 Post by Aacini » 29 Dec 2011 17:47

It looks good to me, I don't understand what can be wrong... :|

You may include ECHO's at several points to try to discover what the problem is.

aGerman
Expert
Posts: 3063
Joined: 22 Jan 2010 18:01
Location: Germany

Re: infinite loop with break condition

#21 Post by aGerman » 29 Dec 2011 18:06

Thanks.

Just to tell you what happen:
- The factorial algorithm results in an infinte loop.
- The lcm comes up with an endless Division By 0 error.
- The calculation of e displays

Code: Select all

Calculation of e:
#-   #!    term   summation
Number e = .0

Regards
aGerman

Aacini
Expert
Posts: 1465
Joined: 06 Dec 2011 22:15
Location: México City, México

Re: infinite loop with break condition

#22 Post by Aacini » 29 Dec 2011 20:55

I am posting again my complete WHILE macro test program:

Code: Select all

@echo off
setlocal DisableDelayedExpansion
set LF=^


::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

set while=for %%n in (1 2) do if %%n==2 (%\n%
      call :StrLen argv argvLen=%\n%
      set "body=!argv:*do=!"%\n%
      call :StrLen body bodyLen=%\n%
      set /A condLen=argvLen-bodyLen-2%\n%
      for %%a in (!condLen!) do set "cond=!argv:~0,%%a!"%\n%
      echo for /L %%%%w in (1,0,1^^) do if !cond! !body! else exit ^^^^!whileResult^^^^!^> whileBody.bat%\n%
      cmd /Q /C whileBody%\n%
      endlocal ^& set whileResult=^!errorlevel^!%\n%
   ) else setlocal EnableDelayedExpansion ^& set argv=

set !=^^^^!

setlocal EnableDelayedExpansion

call :factorial 10 factor=
echo Factorial of 10: %factor%
echo/
echo/

call :lcm 3527 3784 var=
echo Least common multiple of 3527 and 3784: %var%
echo/
echo/

echo Calculation of e:
echo/
set /A digits=8, one=1
for /L %%i in (1,1,%digits%) do set one=!one!0
set /A num=0, fact=1, term=1, whileResult=0
echo #- #%!%   term=1/#%!%    summation
%while% %!%term%!% gtr 0 do ( %\n%
   set /A term=one/fact, whileResult+=term %\n%
   echo %!%num%!%- %!%fact%!%    %!%term%!%    %!%whileResult%!% %\n%
   set /A num+=1, fact*=num %\n%
)
echo/
echo Number e = !whileResult:~0,-%DIGITS%!.!whileResult:~-%DIGITS%!
echo/
echo/

goto :EOF


:factorial N R=
setlocal
set num=1
set whileResult=1
%while% %!%num%!% leq %1 do ( set /A whileResult*=num, num+=1 )
endlocal & if "%2" neq "" (set %2=%whileResult%) else echo %whileResult%
exit /B

:lcm n1 n2 lcm=
setlocal
set /a "whileResult=%1, j=%2"
%while% %!%j%!% neq 0 do ( set /a k=j, j=whileResult%%%%j, whileResult=k )
set /a j=%1*%2/whileResult
endlocal & if "%3" neq "" (set %3=%j%) else echo %j%
exit /B


:StrLen string [result=[adjust]]
setlocal EnableDelayedExpansion
set str=%1
set str=!str:"= !
if "!str:~0,1!" == " " (
    set "str=0%~1"
) else (
    set "str=0!%1!"
)
set len=0
for /L %%A in (12,-1,0) do (
   set /A "len|=1<<%%A"
   for %%B in (!len!) do if "!str:~%%B,1!" == "" set /A "len&=~1<<%%A"
)
for %%v in (!len!) do endlocal&if not "%2" == "" (set /A "%2=%%v%3") else echo %%v
exit /B

Results:

Code: Select all

Factorial of 10: 3628800


Least common multiple of 3527 and 3784: 13346168


Calculation of e:

#- #!   term=1/#!    summation
0- 1    100000000    100000000
1- 1    100000000    200000000
2- 2    50000000    250000000
3- 6    16666666    266666666
4- 24    4166666    270833332
5- 120    833333    271666665
6- 720    138888    271805553
7- 5040    19841    271825394
8- 40320    2480    271827874
9- 362880    275    271828149
10- 3628800    27    271828176
11- 39916800    2    271828178
12- 479001600    0    271828178

Number e = 2.71828178

Please test it and report what happened... :?

aGerman
Expert
Posts: 3063
Joined: 22 Jan 2010 18:01
Location: Germany

Re: infinite loop with break condition

#23 Post by aGerman » 30 Dec 2011 08:21

Still wont work. The same behaviour like before :(

Regards
aGerman

Aacini
Expert
Posts: 1465
Joined: 06 Dec 2011 22:15
Location: México City, México

Re: infinite loop with break condition

#24 Post by Aacini » 30 Dec 2011 15:37

I don't understand what is happening. :? Are you using Win XP? Perhaps someone may have another idea? :?

dbenham
Expert
Posts: 1961
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: infinite loop with break condition

#25 Post by dbenham » 30 Dec 2011 16:36

I get the exact same results as aGerman on both Vista and XP.

Dave Benham

aGerman
Expert
Posts: 3063
Joined: 22 Jan 2010 18:01
Location: Germany

Re: infinite loop with break condition

#26 Post by aGerman » 30 Dec 2011 17:02

I'm on Win7.

I figured out what we have to do:

Code: Select all

      cmd /V:ON /Q /C whileBody%\n%

/V:ON ! But why :?: Why does it not inherit the delayed expansion?

Regards
aGerman

dbenham
Expert
Posts: 1961
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: infinite loop with break condition

#27 Post by dbenham » 30 Dec 2011 18:44

aGerman wrote:

Code: Select all

      cmd /V:ON /Q /C whileBody%\n%

/V:ON ! But why :?: Why does it not inherit the delayed expansion?


I've run into this before. About the only thing that is inherited upon instantiation of a new CMD are the environment variable values.

Delayed expansion state, Command extension state, Echo state - they all revert back to the default state based on the registry. This is also true for implicit instantiation of CMD that occurs with FOR /F %%A IN ('some command'), and also on both sides of a pipe. I learned the significance of this at Why does delayed expansion fail when inside a piped block of code?


Now that you've got Aacini's code working, I'll have to try to figure out how it works.

Dave Benham

Aacini
Expert
Posts: 1465
Joined: 06 Dec 2011 22:15
Location: México City, México

Re: infinite loop with break condition

#28 Post by Aacini » 30 Dec 2011 19:34

WHILE macro is simple to use because it resemble an original Batch command, like IF or FOR. However, it is slow when it is repeatedly executed because the complete while-body is created again each time it is executed. I slightly modified WHILE macro, and renamed it to WHILEBODY, so it just create the whileBody.bat file, so it can be directly executed several times via CMD /V:ON /Q /C in a faster way. This modification also allows to execute whileBody.bat with parameters.

Code: Select all

@echo off
setlocal DisableDelayedExpansion
set LF=^


::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

set whileBody=for %%n in (1 2) do if %%n==2 (%\n%
      call :StrLen argv argvLen=%\n%
      set "body=!argv:*do=!"%\n%
      call :StrLen body bodyLen=%\n%
      set /A condLen=argvLen-bodyLen-2%\n%
      for %%a in (!condLen!) do set "cond=!argv:~0,%%a!"%\n%
      echo for /L %%%%w in (1,0,1^^) do if !cond! !body! else exit ^^^^!whileResult^^^^!^> whileBody.bat%\n%
      endlocal%\n%
   ) else setlocal EnableDelayedExpansion ^& set argv=

set !=^^^^!

setlocal EnableDelayedExpansion

%whileBody% %!%j%!% neq 0 do ( set /a k=j, j=whileResult%%%%j, whileResult=k )

call :lcm 3527 3784 var=
echo Least common multiple of 3527 and 3784: %var%
goto :EOF


:lcm n1 n2 lcm=
setlocal
set /a "whileResult=%1, j=%2"
cmd /V:ON /Q /C whileBody
set /a j=%1*%2/%errorlevel%
endlocal & if "%3" neq "" (set %3=%j%) else echo %j%
exit /B

The whileBody.bat file may be renamed after was created, so WHILE or WHILEBODY macros may create a second whileBody.bat file. This way, a while loop may be nested inside another one.

Code: Select all

%whileBody% %!%j%!% lss 10 do (%\n%
   set /A mul=i*j              %\n%
   set /P =%!%mul%!%^< NUL     %\n%
   set /A j+=1                 %\n%
)
if exist whileBodyRow.bat del whileBodyRow.bat
ren whileBody.bat whileBodyRow.bat

echo Multiplication Table
echo/
set i=1
%while% %!%i%!% lss 10 do (     %\n%
   set j=1                      %\n%
   cmd /V:ON /Q /C whileBodyRow %\n%
   echo/                        %\n%
   set /A i+=1                  %\n%
)

Results:

Code: Select all

Multiplication Table

1     2     3     4     5     6     7     8     9
2     4     6     8     10     12     14     16     18
3     6     9     12     15     18     21     24     27
4     8     12     16     20     24     28     32     36
5     10     15     20     25     30     35     40     45
6     12     18     24     30     36     42     48     54
7     14     21     28     35     42     49     56     63
8     16     24     32     40     48     56     64     72
9     18     27     36     45     54     63     72     81

aGerman
Expert
Posts: 3063
Joined: 22 Jan 2010 18:01
Location: Germany

Re: infinite loop with break condition

#29 Post by aGerman » 30 Dec 2011 20:34

dbenham wrote:Delayed expansion state, Command extension state, Echo state - they all revert back to the default state based on the registry. This is also true for implicit instantiation of CMD that occurs with FOR /F %%A IN ('some command'), and also on both sides of a pipe. I learned the significance of this at Why does delayed expansion fail when inside a piped block of code?

Yeah, my tests confirmed that. Thanks for the linked thread, very helpful and interesting!

Regards
aGerman

Rileyh
Posts: 147
Joined: 01 Sep 2011 03:54
Location: Perth, Western Australia

Re: infinite loop with break condition

#30 Post by Rileyh » 01 Jan 2012 02:33

The simplest way that I have found to do it is this:

Code: Select all

set "n=0"
:loop
set /a n+=1
if /i _"%n%"==_"10" (goto :exitloop)
ping -n 10 1.1.1.1
goto :loop
:exitloop
exit /b


Why (and how!) do you use macros to do infinite loops? And <b>aGerman</b>'s original topic post, it is not an infinite loop, it breaks after a set loop number.

Regards,
Rileyh

Post Reply