UnderwareDESIGN

PlayBASIC => Resources => Tutorials => Topic started by: kevin on January 22, 2007, 07:27:41 AM

Title: Should I use GOTO in my BASIC programs ?
Post by: kevin on January 22, 2007, 07:27:41 AM

(http://underwaredesign.com/PlayBasicSig.png) (http://www.playbasic.com)


Quote ok, what is so bad about the goto command ?

    There's really nothing wrong with the command, it's more the misuse/over reliance that tends to produce code that is less than desirable.  Which is commonly referred to as spaghetti code.  

   Goto (http://playbasic.com/help.php?page=CORE%20COMMANDS.GOTO)'s purpose is an explicit change of control.  While it can be used to create loops, sub routines etc etc, there's really little point in emulating (via goto) the functionality of the language you're using.  As modern implementations of BASIC already offer these mechanisms (ie.  Do/Loop/Exit  -Gosub/Return).    


.Example #1  Using Goto to emulate DO/Loop/Exit structures.

[pbcode]
    i=1000

Main:
    if I<0 then Goto ExitMainLoop
    i=i-1
   Goto Main

ExitMainLoop:
   print "End of program"
   sync
 waitkey
[/pbcode]


#2 Do/Loop/Exit  variation

[pbcode]
    i=1000
  Do
      if I<0 then Exit
       i=i-1
   loop

   print "End of program"
   sync
   WaitKey
[/pbcode]

  Examples #2 and #1 demonstrate the same program flow using the either method.   The code loops a finite number of times  before exiting and continuing on with the rest of the program.    The main benefit of Do/loop option over goto's is we remove the need for the explicit LABELS.   Now since Labels are unique to a particular scope (Although this does vary between BASIC dialects).  Meaning we can easily clash label names, or worse, create logical errors in our programs, by jumping to the wrong label.

  To demo that,  Here's a variation of Example #1 with intentionally broken logic.

#1b  Faulty Logic

[pbcode]
    i=1000

MainLP1:
    if I<0 then Goto ExitMainLoop1
    i=i-1
   Goto MainLP1

ExitMainLoop1:
   print "End of  Loop #1"

    i=1000
MainLP2:
    if I<0 then Goto ExitMainLoop1

    i=i-1
   Goto MainLP2

ExitMainLoop2:
   print "End of  Loop #2"
   print "End of Program"
   sync
 waitkey
[/pbcode]

  If you look carefully, following the logic through,  you'll should notice a logic  error in the second loop.  Now when I  is lower than zero in the second loop, we want it to jump out of that loop to it's Exit label  ExitMainLoop2  and thus end the program.  But due to a typo or oversight on the programmers part, the exit label is pointing back to the previous exit point.  So the GOTO jumps back to ExitMainLp1 again.   This causes the program to fall into an infinite loop.   Which is not something the compiler can easily detect.  Its a human error and an all too common mistake



Example #2b  A Do/loop version.

[pbcode]
    i=1000

 Do
    if I<0 then  exit
    i=i-1
  loop

   print "End of  Loop #1"

    i=1000
   do
       if I<0 then exit
       i=i-1
  loop
   print "End of  Loop #2"
   print "End of Program"
   sync
 waitkey
[/pbcode]

  In this variation of the example,  we're not only been able to remove a few lines/labels from the program, but we rely upon the integrity of the programs logic, to do as we intend.    This is made possible by how EXIT works,  as it's behavior is fixed.   That is to say,  it will always jump us out and to the bottom of a LOOP structure (DO/LOOP,  Repeat/Until - While/EndWhile - For / Next), so control is changed  to the  line following the closing loop statement.  (Which in this case, is the line after LOOP instructions)

Example #3  Using Goto to emulate Gosub/Return

[pbcode]
result=1
i=16
Main:

if I<0 then Goto ExitMainLoop

; jump to our sub routine
  goto MySubroutine1

; this Label is where our previous sub routine jumps back to
MainLoopLabel1:

 i=i-1
  Goto Main

ExitMainLoop:
   print "End of program"
   print result
  sync
  waitkey
  END


MySubroutine1:
  result=result+result
  Goto MainLoopLabel1:

[/pbcode]



#4  Gosub  

[pbcode]
 Result=1
 i=16
Main:
  if I<0 then Goto ExitMainLoop

; jump to our sub routine
  gosub MySubroutine1


   i=i-1
  Goto Main

ExitMainLoop:
 print "End of program"
 print result
 sync
 waitkey
 END


MySubroutine1:
; Double the value of result
result=result+result
   ; Tell our program to restore control to after the calling gosub statement
return

[/pbcode]

 While GOTO / GOSUB might appear to have similar functionally,   GOTO is designed to change control where we don't wish to return to this point, while GOSUB is designed to change control to common piece of code, then upon reaching the RETURN instruction, control will revert back to the point of original call.
 Here's a cleaner version of Example #4.  Using Do/Loop and Gosubs

[pbcode]
Result=1
i=16

; start of Do/loop structure
do

if I<0 then Exit

; jump to our sub routine
gosub MySubroutine1

i=i-1
loop

; end of program
print "End of program"
print result
sync
waitkey
END


MySubroutine1:
; Double the value of result
result=result+result
   ; Tell our program to restore control to after the calling gosub statement
return
[/pbcode]



 If you've made it this far, then I commend you. This is certainly a bit of a yawn fest. Anyway the previous example(s) could further simplified by using  FOR/NEXT or  Repeat/UNTIL  loop structures also.  Both of which are better fit for programs flow (logic) shown here.  So here's a version using those controls too.




Repeat Until verion

[pbcode]
Result=1
i=16

; start of REPEAT/UNTIL structure
 repeat

; jump to our sub routine
 gosub MySubroutine1

 i=i-1
 until i<0

; end of program
 print "End of program"
 print result
 sync
 waitkey
 END


MySubroutine1:
 ; Double the value of result
    result=result+result
 ; Tell out program to restore control to after the calling gosub statement
    return
[/pbcode]


For Next

[pbcode]
Result=1

; start of For/NEXT structure
  for i=0 to 16

 ; jump to our sub routine
   gosub MySubroutine1

  next i

; end of program
  print "End of program"
  print result
  sync
  waitkey
  END


MySubroutine1:
 ; Double the value of result
  result=result+result
  ; Tell out program to restore control to after the calling gosub statement
  return
[/pbcode]



Return To PlayBasic Tutorial Index (http://www.underwaredesign.com/forums/index.php?topic=2552.0)

Read PlayBASIC Help Files  (https://playbasic.com/help.php)




Title: Re: Should I use GOTO in my programs ?
Post by: kevin on January 22, 2007, 08:50:56 AM
Quote Never use GOTO to exit any kind of loop (and I mean any kind of loop for-next or otherwise)


  While this was true back in the day, as some older variants of BASIC  did use the stack to store the current loops starting address,  but today however, this is actually False.    

   I.e.

 [pbcode]
Do
cls 0

for lp2=0 to 1000

  For InnerLP=1 to 10
    goto AvoidStackPop
  next Innerlp
AvoidSTackPop:

 inc Calls
 next lp2

 print "Number Of Goto Exit Calls:"+STR$(Calls)
 Sync
loop

 [/pbcode]

   I would like to point out that while GOTO is a legal way to exit a loop structure, use EXIT command, that's what it's there for  !  



 
QuoteI'm using Gosub - but my program dies after a while with a Stack OverFlow Error ?

   The Gosub control change allows us to call (change control to) common pieces of code, referred to sub routines.  Once a sub routine is complete, you're expected to use the Return command to restore program execution back to the point after the original gosub call.

  What actually happens is that when the GOSUB command is executed, the language pushes the Address (actually it's the position in memory of the next line of code)  to the STACK (an area where temp data is stored).   After the push, the language jumps to the new code section (the subroutine) in the program.  

  When our program reaches the RETURN command,  it does the opposite.  So this time, it Pop's the last value off the stack.  This value will be address of the next command after the original Gosub call.   Therefore, if you missed calling RETURN or after a gosub, then your program will inevitably die from overflowing the temporary stack area.   As the data is being pushed onto the stack, but never removed.  

 Here's an example of an unresolved Gosub.  This will most certainly cause a Stack overflow sooner or later !

[pbcode]
Do
cls 0

  For lp=0 to 10
   gosub StackOverFlow
StackOverFlow:
   inc calls
  next lp

print "Approx Stack Size:"+STR$(Calls*4)
Sync
loop
[/pbcode]


Return To PlayBasic Tutorial Index (http://www.underwaredesign.com/forums/index.php?topic=2552.0)




Title: How can I EXIT from a nested loop structure ?
Post by: kevin on January 22, 2007, 10:59:12 PM
QuoteHow can I EXIT from a nested loop structure ??

  EXIT as you most probably already know, only forces the program to abort the current loop structure.   So upon execution, control is passed to the next operation outside of the current loop.  But what if we wish to abort execution of more than one loop structure ?

   This is one of the short comings of EXIT command in most languages.   While some languages like PlayBasic for example have various versions of the EXIT mechanism for each loop structure, but . if ALL you have is EXIT then you have a few options.

  The First option comes from my Structured Programming days way back when I was in  high school (mid 1980's).   In those days the EXIT/BREAK weren't common place commands and loop structures were more often than not STACK based.  In this situation,  we'd simulate the EXIT control by either stepping the LOOP counter to Passed the end of loop (in FOR/NEXT 's), or setting the complete loop conditional flags (Repeat/ Until,  While / EndWhile) so the loop finished after this iteration.


Eg #1.  Breaking out of a For/NEXT without a EXIT or GOTO

[pbcode]
For lp=0 to 10
; When lp ='s 5, we'll force the loop the terminate
if lp=5
; set the For Loop counter beyond the END of the declared range
Lp = 10+1
else
; if the lp is <> to 5 we print the loop counter
Print lp
endif
next

; Refresh the screen and wait for a key press
Sync
Waitkey

[/pbcode]


Eg #1b  This is the equivalent code above using the EXIT to break out of the loop.

[pbcode]
; Simplified the loop termination using EXIT.
For lp=0 to 10

; When lp ='s 5, we'll force the loop the terminate using exit
if lp=5 then EXIT

; if the lp is <> to 5 we print the loop counter
Print lp

Next

; Refresh the screen and wait for a key press
Sync
Waitkey

[/pbcode]


  The examples above are only single loops though,  so lets look at how we can break out of Nested For/NEXT loop  situations.  In the following example, the code fills a 2D array with random values,  then preforms a nested linear search upon it looking for the first occurrence of particular value.   Not a very impressive example, but it's something to use as our test frame work.

  First we'll use the setting the loop counters method to eject.  The main difference with the first example, is that in order to eject from both loops, we have force both loop counters beyond their End ranges.



 Eg #2.  Breaking out of  nested For/NEXT without a EXIT or GOTO

[pbcode]
; Set the Width + Height of our 2D Array
Width=10
Height=10
Dim Table(Width,Height)

; Fill the table Array with some random values,between zero and 20
For Ylp=0 to Height
Row$=""
For Xlp=0 to Width
Table(Xlp,Ylp)=rnd(20)
Row$=Row$+digits$(table(Xlp,ylp),2) +","
next
print trimright$(Row$,",")
next


; Search for the first Find How values in our test array are the
FindValue=15


; Flags to hold info about the first found item
FoundFlag=false
FoundAtX=0
FoundAtY=0

For Ylp=0 to Height

For Xlp=0 to Width

; check if this ccell is the array is our value
if Table(Xlp,Ylp)=FindValue

; Set out Found Flag to TRUE
FoundFlag=true

; remember the location it was within the array
FoundAtX = Xlp
FoundAtY = Ylp

; Force the inner loops counter to END by setting the Xlp counter beyond
; the loops range
Xlp=Width+1

; Force the outter loops counter to END by setting the Ylp counter beyond
; the loops range
Ylp=Height+1
endif

next Xlp
next Ylp


If FoundFlag=true
print "Found "+str$(FindValue)+" at "+str$(FoundAtX)+"x,"+str$(FoundAtY)+"y"
else
print "Value Not Found"
endif


; Refresh the screen and wait for a key press
Sync
Waitkey

[/pbcode]

 Pros:

  * Assuming there's no code following the inner loop, this method doesn't add any extra status checks to the outer loop.
  * Suitable in languages that use Stack base loop structures.
  * Keeps a One entry -> One Exit flow of your code.   Your teacher will love you :)


 Cons:
    * In order to eject, you have to know the direction and loop counter end boundary.



 Eg #2b.  Breaking out of  nested For/NEXT using EXIT.

 This is where Exit can actually be a little clumsy. As  if use EXIT to break out of the inner loop once a match is found, this will not stop the outer loops from executing.  So if we wish to purely use thr EXIT method, we're force to test our FoundFlag condition and EXIT the outer loop if that condition was met.        

[pbcode]

; Set the Width + Height of our 2D Array
Width=10
Height=10
Dim Table(Width,Height)

; Fill the table Array with some random values, between zero and 20
For Ylp=0 to Height
Row$=""
For Xlp=0 to Width
Table(Xlp,Ylp)=rnd(20)
Row$=Row$+digits$(table(Xlp,ylp),2) +","
next
print trimright$(Row$,",")
next


; Search for the first Find How values in our test array are the
FindValue=15


; Flags to hold info about the first found item
FoundFlag=false
FoundAtX=0
FoundAtY=0

For Ylp=0 to Height

For Xlp=0 to Width

; check if this ccell is the array is our value
if Table(Xlp,Ylp)=FindValue

; Set out Found Flag to TRUE
FoundFlag=true

; remember the location it was within the array
FoundAtX = Xlp
FoundAtY = Ylp

; exit this Xlp loop
exit
endif
next Xlp

; check if we've found our item, if so, exit this loop also
if FoundFlag then exit
next Ylp


If FoundFlag=true
print "Found "+str$(FindValue)+" at "+str$(FoundAtX)+"x,"+str$(FoundAtY)+"y"
else
print "Value Not Found"
endif


; Refresh the screen and wait for a key press
Sync
Waitkey
[/pbcode]

 Pros:

  * You don't need to know the loop direct/ or length in order to use this method
  * Suitable in languages that use Stack base loop structures.
  * Keeps a One entry -> One Entry line to your code.

 Cons:

    * Using EXIT to eject from both loops requires, trapping the inner and outter loop condition and exiting.  So it adds a state check to the Outer loop ion this case.   You can combine methods to avoid this however (Ie.  Exiting the inner loop and setting the outer loops counter beyond it's end point)
     
 Note:  PlayBASIC has a ExitFOR command purely for this purpose. The command allows you to exit from a user defined for/next level.   In this case, when a match is found, you'd simple Exit from the YLP using  ExitFor Ylp)  


 Eg #2c.  Breaking out of  nested For/NEXT loops using the dreaded GOTO.

  This version of the example uses GOTO to eject from the pair of loops once a match is found.  

[pbcode]
; Set the Width + Height of our 2D Array
Width=10
Height=10
Dim Table(Width,Height)

; Fill the table Array with some random values,between zero and 20
For Ylp=0 to Height
Row$=""
For Xlp=0 to Width
Table(Xlp,Ylp)=rnd(20)
Row$=Row$+digits$(table(Xlp,ylp),2) +","
next
print trimright$(Row$,",")
next


; Search for the first Find How values in our test array are the
FindValue=15


; Flags to hold info about the first found item
FoundFlag=false
FoundAtX=0
FoundAtY=0

For Ylp=0 to Height

For Xlp=0 to Width

; check if this ccell is the array is our value
if Table(Xlp,Ylp)=FindValue

; Set out Found Flag to TRUE
FoundFlag=true

; remember the location it was within the array
FoundAtX = Xlp
FoundAtY = Ylp

; jump out of both loops and continue on.
goto FoundItemContinue
endif

next Xlp
next Ylp

FoundItemContinue:


If FoundFlag=true
print "Found "+str$(FindValue)+" at "+str$(FoundAtX)+"x,"+str$(FoundAtY)+"y"
else
print "Value Not Found"
endif


; Refresh the screen and wait for a key press
Sync
Waitkey
[/pbcode]



 Pros:

   * You don't need to know the loop direction or length in order to use this method
   * Does NOT Keep a One entry -> One Entry flow to you code.   Your teacher will fail you ! :)
   * Allows immediate ejection of any number of nested loop types

 Cons:

   * Not Suitable in languages that use Stack base loop structures.

   * Requires a Hard code label as the ejection point.  Which if your not careful can lead to logic errors.
     

  In terms of performance, there really isn't going to be any significant difference.  It's more a question a style, which every body has a different opinion about ! :)



Return To PlayBasic Tutorial Index (http://www.underwaredesign.com/forums/index.php?topic=2552.0)



   
Title: How NOT to use GOTO!
Post by: kevin on January 28, 2007, 07:34:07 AM
How NOT to use GOTO


 While on the subject of GOTO..   The following is one of my favorite Bad examples of  a 'goto at all costs' design.   I don't recall who sent it, or the program per verbatim for that matter, but the design used a kind of marker system to select the jump back point when calling subroutines via goto.   Here I've simulated this with a DrawCircle routine.  If you follow it through you'll see that upon conclusion of DrawCircle routine, we're using a select/case to direct control back to the main loop..   NASTY

Eg 1a

[pbcode]

;  Here's an example of how you should ___NOT____ use GOTO..


MainLoop:
   Cls rgb(0,0,0)


   ReturnPoint=1

    Xpos=mouseX()
    Ypos =Mousey()
   ink rgb(255,0,0)
    Goto DrawCircle


MainLoop1:

   ReturnPoint=2

   Circle1X =(Circle1X+1) and 255
   Circle1Y =(Circle1Y+1) and 255

   Xpos=Circle1X
   Ypos=Circle1Y
   ink rgb(0,255,0)
   Goto DrawCircle


MainLoop2:


   ReturnPoint=3
   While Circle2SpeedX=0
             Circle2SpeedX =rndrange(-5,5)
   endwhile
      
   While Circle2SpeedY=0
             Circle2SpeedY =rndrange(-5,5)
   endwhile

   if Circle2Init=false
         circle2X=rnd(800)
         circle2Y=rnd(800)
         Circle2Init=true
   endif
   
   Circle2X =Circle2X+Circle2SpeedX
   Circle2Y =Circle2Y+Circle2SpeedY

   if POintInBox(Circle2X,circle2y,0,0,800,600)=false
      Circle2init=0
      Circle2SpeedX=0
      Circle2SpeedY=0
   endif

   Xpos=Circle2X
   Ypos=Circle2Y
   ink rgb(0,0,255)
    Goto DrawCircle
   
   
MainLoop3:


   
   Sync
   Goto Mainloop
   
   
   

DrawCircle:

   c=getink()   
   Circle  Xpos,Ypos,50,true
   Circlec  Xpos,Ypos,25,true,rgbfade(C,50)
     

   ; Handle Return
   select ReturnPOint
         case 1 : Goto MainLoop1
         case 2 : Goto MainLoop2
         case 3 : Goto MainLoop3
   endselect

   ; unknown return point.. so we error
   Cls rgb(255,0,0)
   print "Call Back Failed"
   sync
   waitkey
   end
   

[/pbcode]



 Obviously the flow of the program outlined above can be recreated using more appropriate constructs  (Gosub/Return,  Functions).  

Eg 1b.

[pbcode]

;  Here's a cleaner version using using subroutines via GOSUB/RETURN


   Do
      Cls rgb(0,0,0)


      ; Mouse Control circle 1 ---------------------------

       Xpos=mouseX()
       Ypos =Mousey()
       ink rgb(255,0,0)
       Gosub DrawCircle


      ; Control circle 2 ---------------------------------
   
         Circle1X =(Circle1X+1) and 255
         Circle1Y =(Circle1Y+1) and 255

         Xpos=Circle1X
         Ypos=Circle1Y
         ink rgb(0,255,0)
         gosub DrawCircle



      ;  Control circle #3 -------------------------------

         While Circle2SpeedX=0
                Circle2SpeedX =rndrange(-5,5)
         endwhile      
         While Circle2SpeedY=0
                Circle2SpeedY =rndrange(-5,5)
         endwhile
         if Circle2Init=false
               circle2X=rnd(800)
               circle2Y=rnd(800)
               Circle2Init=true
         endif
   
         Circle2X =Circle2X+Circle2SpeedX
         Circle2Y =Circle2Y+Circle2SpeedY

         if POintInBox(Circle2X,circle2y,0,0,800,600)=false
            Circle2init=0
            Circle2SpeedX=0
            Circle2SpeedY=0
         endif

         Xpos=Circle2X
         Ypos=Circle2Y
         ink rgb(0,0,255)
          Gosub DrawCircle
   

   Sync
   loop
   
   
   

DrawCircle:

   c=getink()   
   Circle  Xpos,Ypos,50,true
   Circlec  Xpos,Ypos,25,true,rgbfade(C,50)
     
  return   



[/pbcode]


Eg 1c.   (Function Version)

[pbcode]
;  Here's another version using FUNCTIONS.. As you can see it's cleaner again


   Do
      Cls rgb(0,0,0)


      ; Mouse Control circle 1 ---------------------------

         DrawCircle(MouseX(),MouseY(),Rgb(255,0,0))


      ; Control circle 2 ---------------------------------
   
         Circle1X =(Circle1X+1) and 255
         Circle1Y =(Circle1Y+1) and 255

         DrawCircle(Circle1X,Circle1Y,Rgb(0,255,0))




      ;  Control circle #3 -------------------------------

         While Circle2SpeedX=0
                Circle2SpeedX =rndrange(-5,5)
         endwhile      
         While Circle2SpeedY=0
                Circle2SpeedY =rndrange(-5,5)
         endwhile
         if Circle2Init=false
               circle2X=rnd(800)
               circle2Y=rnd(800)
               Circle2Init=true
         endif
   
         Circle2X =Circle2X+Circle2SpeedX
         Circle2Y =Circle2Y+Circle2SpeedY

         if POintInBox(Circle2X,circle2y,0,0,800,600)=false
            Circle2init=0
            Circle2SpeedX=0
            Circle2SpeedY=0
         endif

         DrawCircle(Circle2X,Circle2Y,Rgb(0,0,255))


   Sync
   loop
   
   
   

Function DrawCircle(xpos,Ypos,ThisColour)
   Circlec  Xpos,Ypos,50,true,ThisColour
   Circlec  Xpos,Ypos,25,true,rgbfade(ThisColour,50)
EndFunction
     
[/pbcode]




Articles On The Dreaded GOTO

    Goto is one of those commands that invokes such passionate  responses from people (pro & con).   I often find stemming from where/whom the person learned programming from.   When I was high school in the 80's,  one our computer science teachers required everything streamlined to be one entry->one exit.   A friend of mine took great pleasure in getting into such debates with him.  But anyway..  The following article causes a bit of stir when it written.      

  Donald E. Knuth (Structured programming with go to statements (1974) (http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.103.6084)
  http://en.wikipedia.org/wiki/Goto (http://en.wikipedia.org/wiki/Goto)





Return To PlayBasic Tutorial Index (http://www.underwaredesign.com/forums/index.php?topic=2552.0)