I hate to disagree with the wise words of House of Pain,
but excessive jumping around is the most common issue I see when
reviewing others’ TP code. Labels and jump-statements come natural in a
language that doesn’t support actual code-blocks for simple
if-statements. Within just a few minutes of programming, a beginner sees
an IF (...),JMP LBL[X]
and decides that this is how FANUC programming
is done.
Randall Munroe from xkcd sums up the potential for catastrophe when using GOTO statements (TP’s JMP) in this comic:
Unfortunately you can’t avoid them completely. TP only supports FOR and SELECT control structures, and it only supports them in a very limited fashion. IF-statements require jumps, there’s no such thing as an ELSE in TP so you have to do it with a jump, and WHILE-loops don’t exist; their functionality has to be done with a couple labels and a jump.
Here’s how each of these control structures is implemented in TP on the left and a more fully-featured programming language implementation (TP+) on the right:
! TPP ; # TP+
! ----------------------- ; # ----------------------
;
; the_input := DI[1]
; the_register := R[1]
;
! simple if statement ; # simple if statement
! ----------------------- ; # ----------------------
IF DI[1]=OFF,JMP LBL[100] ; if the_input
! DI[1] is ON ; # the_input is ON
LBL[100] ; end
;
! if-else block ; # if-else block
! ----------------------- ; # ----------------------
IF DI[1]=OFF,JMP LBL[100] ; if the_input
! DI[1] is ON ; # the_input is ON
JMP LBL[101] ; else
LBL[100] ; # the_input is OFF
! DI[1] is OFF ; end
LBL[101] ;
;
! while loop ; # while loop
! ----------------------- ; # ----------------------
LBL[100] ; while the_input
IF DI[1]=OFF,JMP LBL[101] ; # the_input is on
! DI[1] is ON ; end
JMP LBL[100] ;
LBL[101] ;
;
! select statement ; # select statement
! ----------------------- ; # ---------------------
SELECT R[1]=1,JMP LBL[100] ; case the_register
=2,JMP LBL[101] ; when 1
ELSE,JMP LBL[102] ; # the_register == 1
; when 2
LBL[100] ; # the_register == 2
! R[1]=1 ; else
JMP LBL[103] ; # the_register is not 1 or 2
; end
LBL[101] ;
! R[1]=2 ;
JMP LBL[103] ;
;
LBL[102] ;
! R[1] is not 1 or 2 ;
JMP LBL[103] ;
;
LBL[103] ;
Which side is easier to read? Which side makes it easier to make a mistake by jumping to an incorrect label?
Here’s an example of a bad main routine that’s similar to a lot of programs I see:
LBL[1] ;
IF (...some string of conditions...),JMP LBL[100] ;
IF (...some other conditions...),JMP LBL[100] ;
IF (...something else...),JMP LBL[200]
;
LBL[2] ;
IF (...another thing...),JMP LBL[300] ;
;
IF (..a couple of condition...),CALL SOME_ROUTINE ;
;
LBL[100] ;
! do something ;
IF (.....),JMP LBL[2] ;
;
LBL[200] ;
IF (...),JMP LBL[100] ;
;
! do something else ;
JMP LBL[1] ;
;
LBL[300] ;
! one more thing ;
IF (...),JMP LBL[999] ;
JMP LBL[1] ;
;
LBL[999] ;
That code is not an exaggeration. Can anyone tell me what the hell this program is supposed to do? Probably not. This is the part where the dinosaur comes in and bites your head off.
I would argue that you should almost only use labels when implementing these simple control structures. If you are jumping around in your code for any other reason, there’s probably a better way to do it.
I pretty much only use labels and jumps in the following situations:
- Implementing non-existant control structures explained earlier
- Implementing guards to protect the robot or equipment from bad decisions
- Error recovery without using separate routines
Item #2 could probably be considered a standard control structure, and maybe item #3 is actually a good candidate for refactoring. So there you have it: only use labels and jumps when implementing control structures like if-else statements, while loops, complex for loops and more complicated select-case statements that don’t exist in TP. If you want to save yourself some pain, just let TP+ implement those features for you. (But maybe not quite just yet… I still consider TP+ to be in the alpha development stage.)