19. PROGRAM CONTROL GOSUB A Procedure can be called in one of the following ways: GOSUB proc @proc ! my favourite proc I prefer the @-method, because this is spotted easily in a listing. Also, the same method can be used to call a function. Procedures in which you use an 'OK-flag' (to confirm that some operation was successful) can be substituted by Functions: @proc(1,flag!,x) IF flag! ' do this ELSE ' do that ENDIF ' PROCEDURE proc(a,VAR ok!,b) IF a=1 b=10 ok!=TRUE ELSE b=0 ok!=FALSE ENDIF RETURN Using a Function this becomes: IF @func(1,x) ' do this ELSE ' do that ENDIF ' FUNCTION func(a,VAR b) IF a=1 b=10 RETURN TRUE ELSE b=0 RETURN FALSE ENDIF ENDFUNC LOCAL As you know, I am a strong advocate of declaring variables in a Procedure as LOCAL whenever possible. In GFA-Basic local variables are not as local as they should be: global.x=5 PRINT "main program: "; @print @proc_1 PRINT "main program: "; @print ' PROCEDURE print PRINT "global.x=";global.x;" local.x1=";local.x1; PRINT " local.x2=";local.x2 RETURN ' PROCEDURE proc_1 LOCAL local.x1 local.x1=10 PRINT "in Proc_1: "; @print @proc_2 ! change: @proc_2(local.x1) PRINT "in Proc_1: "; @print RETURN ' PROCEDURE proc_2 ! change: proc_2(VAR local.x1) LOCAL local.x2 local.x2=20 PRINT "in Proc_2: "; @print RETURN In Proc_2 you can use local.x1, although it was declared as local in Proc_1. Or in general: a local variable in Procedure P is seen as 'semi- global' by Procedures you call from within Procedure P. I advise you to call variables by reference (with VAR) if you need them. Make the indicated changes in the previous listing and run again. Everything is exactly the same, so why bother? Because in most languages 'local in P' means local for that Procedure only, so you can't use a local variable as 'semi-global' in Procedures you call from Procedure P. E.g. in GFA-Basic for MS-DOS, local.x1 is printed as '0' in Proc_2 in the first listing, but as '10' in the changed listing. If you really need variable local.x1 in Proc_2, you should call it by reference. Try to remember this now and future conversions of your GFA-Basic programs to other languages and/or computers should become easier. ON BREAK GOSUB In a Break-Procedure (activated with ON BREAK GOSUB) you should use ON BREAK CONT to prevent calling the Break-Procedure twice. Nobody can release the keys fast enough: ON BREAK GOSUB break (...) PROCEDURE break ON BREAK CONT ! switch Break off immediately (...) ! do something ON BREAK GOSUB break ! activate Break again RETURN ON ERROR GOSUB It should be possible to use ON ERROR GOSUB in a compiled program: $U+ ON ERROR GOSUB error_procedure (...) $U- PROCEDURE error_procedure ' This Procedure must be located outside the $U+/$U- block RETURN ERROR You can simulate ERRORs with values from -127 to 127. For GFA-errors use values from 0 to 93, for bomb-errors 102 (2 bombs) to 109 (9 bombs) and for TOS-errors -1 to -67. EVERY and AFTER It's not possible to use EVERY and AFTER at the same time because you can use only one interrupt-routine in GFA-Basic. You can only call Procedures without parameters. Both commands don't work during a long PAUSE (or any other command that takes a lot of time). Waiting for any keypress with ~INP(2) is also impossible, you have to use: REPEAT UNTIL LEN(INKEY$) Although one tick for EVERY/AFTER is 1/200th second, you should use multiples of 4 only. The operating system can't digest steps smaller than 1/50th (4/200th) second. Don't make the Procedure too long, or it may be called while being processed! You could start the Procedure with 'EVERY STOP' and end with 'EVERY CONT', but you still need a short Procedure or it will be called again immediately, and again, and again while your main program stops completely. In a compiled program you have to incorporate '$I+ U+', or EVERY and AFTER can't be used. REM Do use comments in your programs, the more the better. Yes, the program will become longer, but it's nice to be able to understand a well- documented program that you've never seen before. Or one of your own masterpieces that you haven't looked at for a couple of years. Don't worry about the speed of your program, except in loops or often called Procedures/Functions. There a comment-line (beginning with REM or ') will slow the interpreter down. A comment after '!' has no influence on the speed of a program, so you can use these everywhere. GOTO You can't use GOTO in a Procedure, a Function or a FOR ... NEXT loop. Some people think you can't use GOTO in other cases either because it's almost unethical to jump around in your program with GOTO. I don't mind as long as you know what you're doing. It is possible to write a well-structured program with a couple of GOTO's. It's also possible to write a spaghetti- program without any GOTO. No, this time I'm not interested in your opinion at all. GOTO DELAY-bug. DELAY-bug The DELAY-command does not operate correctly. During DELAY a Break is impossible. A nastier bug is the appearance of the mouse-cursor during DELAY, even after HIDEM. You are advised to use PAUSE instead: DELAY seconds ! buggy PAUSE seconds*50 ! use this STOP After STOP the GFA-editor does not close channels (open files), so you are able to continue the interrupted GFA-program with CONT. Be careful not to do anything stupid in Direct Mode such as switching your ST off. Use the CLOSE-command in Direct Mode before you do anything that could be dangerous for open files. CHAIN You can CHAIN both GFA-programs (using the interpreter) and compiled programs (from a compiled program): CHAIN "TEST.GFA" ! only possible in interpreted program CHAIN "TEST.PRG" ! only possible in compiled program In a GFA-Basic program all variables and arrays are lost after CHAINing the next program. However, you could use the 160-byte buffer of the scrap- library to pass a short message to the next program: buffer$=SPACE$(160) ! 160 bytes maximum ? message$="this message was sent by the previous program"+CHR$(0) LSET buffer$=message$ r%=SCRP_WRITE(buffer$) ! r%=0 if error CHAIN file$ Read the message with: buffer$=SPACE$(160) r%=SCRP_READ(buffer$) message$=CHAR{V:buffer$} PRINT message$ The use of this buffer is completely illegal, but who cares if you don't use a scrap-library? Unfortunately the GFA-editor seems to think so too, so you should experiment a little before trusting this method. CALL If you load an assembly-routine from disk you can skip the 28-byte header of the program as follows: OPEN "I",#1,file$ length%=LOF(#1)-28 DIM buffer|(length%-1) adr%=V:buffer|(0) SEEK #1,28 BGET #1,adr%,length% CLOSE #1 CALL adr%() ! or ~C:adr%() With an assembly-routine in an INLINE-line you'll get problems after compiling unless the registers a3,a4,a5,a6 and the stackpointer a7 are restored by the routine. Use '$C+' to restore all registers if you don't know whether the routine restores the mentioned registers. EXEC If you are going to run another program (*.PRG) more than once, you'll have to use EXEC 3: base%=EXEC(3,file$,"","") ! load, but don't start yet base$=STR$(base%) cmdl$=CHR$(LEN(base$)+1)+base$ ! create command line (...) r%=EXEC(4,"",cmdl$,"") ! now run it The variable r% contains a value returned by the program (or -39 if not enough memory was available). Repeat the last line if you want to run the loaded program again. Of course you should use EXEC 0 if you're going to run the program one time only. Read the paragraph 'RESERVE' in chapter 4 (page 4-2) if you are going to use EXEC 3. In GFA 3.5 the function EXEC 3 doesn't return an address in base% (GFA-bug). Instead of EXEC 4 you can also use: prg%=base%+256 ! skip the Basepage ~C:prg%([parameter-list]) ! now run it If you call a '*.PRG'-program with EXEC 0, you pass the null-string ("") as the command-line. You need the command-line only if you call a '*.TTP'- program. The command-line is converted to upper case and can't exceed 125 bytes. The first byte of the command-line (usually) determines the length of the line, so the command-line can't contain more than 124 characters. You can use this in a TTP-program (compiled GFA-Basic program, extension changed to TTP) to read the command-line: adr%=BASEPAGE+&H80 last={adr%} FOR i=1 to last cmdl$=cmdl$+CHR$({adr%+i}) NEXT i It's easier to read the command-line with: cmdl$=CHAR{BASEPAGE+&H81} You can use the described method also to examine if a compiled GFA-program (*.PRG) was started directly from the desktop or indirectly through an installed application (see also the paragraph 'Application', page 1-2). If the user double-clicked the program, the command-line is empty. If the user double-clicked an application for the program, the filename of the application can be found in the command-line. Examine the command-line early: after DIR or FSFIRST/FSNEXT the command-line is overwritten by the DTA-buffer. If you would like to run a GFA-program from a compiled program, you can tell the interpreter which file (*.GFA) to load for you: r%=EXEC(0,"\GFABASIC.PRG"," - "+file$,"") You'll have to start the GFA-program in the usual way after the GFA-editor has appeared. Procedures (CHAPTER.19) Execute_prg (page 19-5) EXECUTE Run another program (after reserving enough memory for that program): @execute_prg(program$,50000,"",ok!,r%) ' back to this GFA-Basic program IF ok! PRINT r% ENDIF After reserving 50000 bytes, the program is started. If everything went all right, ok!=TRUE and r% contains the value returned by the program. Scrap_read and Scrap_write (page 19-4) SCRPREAD & SCRPWRIT Use the Scrap-library from a GFA-Basic program: ' in one program do this: @scrap_write("Greetings from one program",ok!) ' in another program do this: @scrap_read(t$,ok!) IF ok! PRINT t$ ENDIF