
**********************************************************************
files: GOSUB+GOTO.TXT, GOSUB+GOTO.TAP
       RetSub.ASM, RetSub.BAS, SubRet1.BAS, SubRet2.BAS, SubGoto.BAS
       PutRAM.ASM
**********************************************************************

** Locating the GO SUB stack
   =========================

The address in ERR_SP (23613) is located between the GO SUB stack and the machine stack, so the most recent GO SUB entry starts at (ERR_SP)+2. That's why the GO SUB and RETURN routines update ERR_SP, as it moves whenever the GO SUB stack is updated. The entries are in 3-byte multiples (return line + statement) terminated by $3E00 (15872), so it can be managed from BASIC if desired. For example, a subroutine can check from where in the program it was called, or an entry can be altered to change the return position.


**********************************************************************


** Break from GO SUB (assembler version)
   =================

Here's a routine to break out of multiple GO SUB levels with a single command, tested on all models from 48k up to +3e. Called from BASIC with RANDOMIZE n+USR retsub, where 'n' is the number of levels to break (n=1 is just the same as a RETURN). Use zero for the level parameter to jump back through all levels at once. Trying to break out of more levels than there have been GO SUBs just gives a "7 RETURN without GO SUB" error.

RetSub.ASM [code]
RETSUB: LD HL, 22                ;Discard all but
        ADD HL, SP               ;(ERR_SP)
        LD SP, HL                ;from m/c stack
        CALL $2dd5       ;FPTOA  ;Get return level
        LD B, A                  ;from calc stack
        EX AF,AF'                ;save return parameter
NXTSUB: POP HL
        POP DE
        LD A, D
        CP $3E                   ;end of GO SUB stack?
        JR Z, NOGSUB
        DEC SP                   ;entry is 3 bytes
        EX (SP), HL              ;restore (ERR_SP)
        EX DE, HL
        LD (23613), SP           ;reset ERR_SP
        PUSH DE
        PUSH HL
        exx
        pop  hl                  ;save line number
        pop  de                  ;and statement number
        exx
        DJNZ NXTSUB
        INC B                    ;reset Zero flag
SETRET: LD BC,$1B76              ;$1B76 = STMT_RET
        PUSH BC                  ;restore STMT_RET
NOGSUB: RES 4, (IY+1)            ;+2A/+3/+3e fix
        JP NZ, $1E73   ;GO_TO_2  ;return through 'n' levels
        EX AF,AF'                ;get return parameter
        AND A                    ;error if not zero
        JP NZ, $1F36   ;REPORT_7 ;'n' was too many levels
        inc  a                   ;reset Zero flag
        push de                  ;restore end marker
        push hl                  ;and (ERR_SP)
        exx                      ;get last line & statement
        JR   SETRET              ;return through all levels
[/code]

At the end, DE & HL will contain:
* the GO SUB stack end marker and error address, if Error 7;
* the statement & line to jump to, if OK;
as required by the ensuing code at the relevant JP address.

Here's the little program which I used for testing, assuming the routine has been loaded at address 'retsub'. FN c() is quite handy as well; it allows relative jumps without needing to code in specific line numbers.

RetSub.BAS [code]
   5 INPUT break: GO SUB FN c()+5: PRINT FN c(): GO TO FN c():
     DEF FN c()=PEEK 23621+256*PEEK 23622
  10 GO SUB FN c()+10: PRINT FN c(): RETURN
  20 GO SUB FN c()+10: PRINT FN c(): RETURN
  30 GO SUB FN c()+10: PRINT FN c(): RETURN
  40 GO SUB FN c()+10: PRINT FN c(): RETURN
  50 GO SUB FN c()+10: PRINT FN c(): RETURN
  60 GO SUB FN c()+10: PRINT FN c(): RETURN
  70 GO SUB FN c()+10: PRINT FN c(): RETURN
  80 GO SUB FN c()+10: PRINT FN c(): RETURN
  90 PRINT FN c(): RANDOMIZE break+USR 23296: REM retsub
[/code]

   =================

** Break from GO SUB (BASIC version)
   =================

Here's an even simpler version, all in BASIC. To use:
[code]
     LET break=number_of_levels_to_RETURN (zero for all levels)
     GO SUB break_routine
[/code]
The routine can be used as it is regardless of where it's put, as long as the four lines of the routine itself have consecutive line numbers. The DEF FNs aren't essential, just convenient.

SubRet1.BAS [code]
   5 INPUT break: GO SUB FN c()+5: PRINT FN c(): GO TO FN c()
  10 GO SUB FN c()+10: PRINT FN c(): RETURN
  20 GO SUB FN c()+10: PRINT FN c(): RETURN
  30 GO SUB FN c()+10: PRINT FN c(): RETURN
  40 GO SUB FN c()+10: PRINT FN c(): RETURN
  50 GO SUB FN c()+10: PRINT FN c(): RETURN
  60 GO SUB FN c()+10: PRINT FN c(): RETURN
  70 GO SUB FN c()+10: PRINT FN c(): RETURN
  80 GO SUB FN c()+10: PRINT FN c(): RETURN
  90 PRINT FN c(): GO SUB 9980

9980 DEF FN c()=FN p(23621): DEF FN p(a)=PEEK a+256*PEEK (a+1):
     DEF FN g(v)=v-FN h(v)*256: DEF FN h(v)=INT (v/256)
9981 FOR b=break-(break>0) TO 0 STEP -1:
       LET gs=FN p(23613)+2: LET gsl=FN p(gs):
       LET gss=PEEK (gs+2): IF FN p(gs+3)>9999 THEN RETURN
9982   POKE gs,FN g(FN c()+1): POKE gs+1,FN h(FN c()+1):
       POKE gs+2,1: RETURN
9983 NEXT b: IF NOT break THEN GO TO FN c()-2
9984 RETURN
[/code]

* FN c()  = the current BASIC line number (PPC)
  FN p(a) = value of 2-byte system variable at address 'a'
  FN g(v) = lo-byte of number 'v'
  FN h(v) = hi-byte of number 'v'
* The RETURN in line 1 is the "break all levels" RETURN. This exit will also be taken if too many levels are requested. 23613=ERR_SP
* The RETURN in line 2 actually goes to line 3 after removing one level from the GO SUB stack.
* The RETURN in line 4 is the actual RETURN to the requested level.


**********************************************************************


** Enhanced RETURN (in BASIC)
   ==========================

* RETURN multiple levels (BREAK from GO SUB)
* RETURN to line+statement

The line 4 RESTORE should refer to the line containing the DEF FN r() statement, which must be the first statement on that line (to facilitate the use of DATADD (23639/70) in locating the function's variables (line 4, statement 2)). For quickest response the DEF FN r() should be the first statement in the program.

Common statements:
[code]
   3 DEF FN r(g,l,s)=g:
     DEF FN c()=FN p(23621): DEF FN p(a)=PEEK a+256*PEEK (a+1):
     DEF FN g(v)=v-FN h(v)*256: DEF FN h(v)=INT (v/256)
   4 RESTORE 3: LET fnr=FN p(23639)+8
[/code]

* FN c()  = the current BASIC line number (PPC)
  FN p(a) = value of 2-byte system variable at address 'a'
  FN g(v) = lo-byte of number 'v'
  FN h(v) = hi-byte of number 'v'

   ==========================

* FN r(g,l,s) to RETURN multiple levels:
  g = GO SUB line number of BfG routine
  l = number of levels to break (l=1 is the same as a plain RETURN)
  s = 0 (not used)
The routine can be used as it is regardless of where it's put, as long as the five lines of the routine itself have consecutive line numbers.

SubRet2.BAS [code]
   3 DEF FN r(g,l,s)=g:
     DEF FN c()=FN p(23621): DEF FN p(a)=PEEK a+256*PEEK (a+1):
     DEF FN g(v)=v-FN h(v)*256: DEF FN h(v)=INT (v/256)
   4 RESTORE 3: LET fnr=FN p(23639)+8

   5 INPUT break: GO SUB FN c()+5: PRINT FN c(): GO TO FN c()
  10 GO SUB FN c()+10: PRINT FN c(): RETURN
  20 GO SUB FN c()+10: PRINT FN c(): RETURN
  30 GO SUB FN c()+10: PRINT FN c(): RETURN
  40 GO SUB FN c()+10: PRINT FN c(): RETURN
  50 GO SUB FN c()+10: PRINT FN c(): RETURN
  60 GO SUB FN c()+10: PRINT FN c(): RETURN
  70 GO SUB FN c()+10: PRINT FN c(): RETURN
  80 GO SUB FN c()+10: PRINT FN c(): RETURN
  90 PRINT FN c(): GO SUB FN r(9980,break,0)

9980 LET break=FN p(fnr+12): REM break=number_of_levels
9981 FOR b=break-(break>0) TO 0 STEP -1:
       LET gs=FN p(23613)+2: LET gsl=FN p(gs):
       LET gss=PEEK (gs+2): IF FN p(gs+3)>9999 THEN RETURN
9982   POKE gs,FN g(FN c()+1): POKE gs+1,FN h(FN c()+1):
       POKE gs+2,1: RETURN
9983 NEXT b: IF NOT break THEN GO TO FN c()-2
9984 RETURN
[/code]

* The RETURN in line 1 is the "break all levels" RETURN. This exit will also be taken if too many levels are requested. 23613=ERR_SP
* The RETURN in line 2 actually goes to line 3 after removing one level from the GO SUB stack.
* The RETURN in line 4 is the actual RETURN to the requested level.

   ==========================

* FN r(g,l,s) to RETURN to line+statement:
  g = GO SUB line number of Gls routine
  l = line number to GO TO
  s = statement number within line

SubGoto.BAS [code]
   3 DEF FN r(g,l,s)=g:
     DEF FN c()=FN p(23621): DEF FN p(a)=PEEK a+256*PEEK (a+1):
     DEF FN g(v)=v-FN h(v)*256: DEF FN h(v)=INT (v/256)
   4 RESTORE 3: LET fnr=FN p(23639)+8

   5 FOR a=1 TO 3: GO SUB FN r(9980,5,a+4): NEXT a: STOP :
     PRINT "a": PRINT "b": PRINT "c": GO SUB FN r(9980,5,3)

9980 LET gln=FN p(fnr+12): LET gst=FN p(fnr+20): PRINT gln,gst
9985 LET gs=FN p(23613)+2:
     POKE gs,FN g(gln): POKE gs+1,FN h(gln): POKE gs+2,gst:
     RETURN
[/code]

The example shows that this function can be used to jump to statements which are normally inaccessible; ie. after the STOP statement. POKEing the system variables NEWPPC & NSPPC can be used for the same purpose (see p.128 of the ZX BASIC manual).


**********************************************************************


** Passing Parameters to Subroutines
   =================================

Initialising a function in this way:

[code]
 100 DEF FN a(a,b,c,d,e,i$,g)=g:
     RESTORE 100: LET da=PEEK 23639+256*PEEK 23640+8:
     POKE 23563,da-INT (da/256)*256: POKE 23564,INT (da/256):
     RETURN
[/code]

enables a parameterised subroutine structure, such that, for example:

[code]
LET i1=VAL "14": LET i2=VAL "15": LET i3=VAL "15": LET i4=VAL "2": LET i5=e1: LET i$="text": GO SUB 400
[/code]

can become:

[code]
     GO SUB FN a(14,15,15,2,e1,"text",400)
[/code]

The function parameters can be referenced in the subroutine by the names used in the DEF FN.

As at least six bytes are being saved on every discarded LET, plain numbers can be used in the parameter list without using any extra memory (although in a practical application most of the function parameters would be passed as variable names anyway), which will provide a slight speed improvement in addition to reducing the size of the program & listing and making it easier to read.

   =================================

I'm using RESTORE 100 to find line 100 in the program; it will set DATADD to the address of the byte immediately preceding the start of line 100. From there +8 gives the address of the function variables space allocated to FN a(). Using the function will store the parameter values (or pointers, for strings) in that space. Setting DEFADD to point to it enables any subsequent code to reference those values by their function parameter names.

With the code as given the variables are global as DEFADD is only set once. To make them local to the subroutine then line 100 would have the POKEs changed to:
 LET dalo=da-INT (da/256)*256: LET dahi=INT (da/256):
and the subroutine would start with:
 POKE 23563,dalo: POKE 23564,dahi
and end with:
 POKE 23564,0
in which case any changes to those variables within the subroutine would not affect any variables with the same names outside the subroutine, and vice versa - although it's probably better not to have global & local variables with the same names, as it's likely to lead to programmer confusion. The ROM routine $28b2 LOOK_VARS only checks the hi-byte of DEFADD when deciding whether or not to look there first, so only the hi-byte needs to be reset to disable that check.

   =================================

It might be worth pointing out that once a sub-routine has been called in this manner the parameter values will be set in the function variables space, so it's not strictly essential to set DEFADD to point to them - it's just a lot easier - as if that's not done the parameters would have to be referenced with PEEK & POKE, rather than being referred to by their names.


**********************************************************************


** CLEAR does not RESTORE the DATA pointer
   =======================================

Despite what it says on pages 124 & 156 of the ZX BASIC manual, CLEAR does not do a RESTORE. On checking the ROM disassembly it can be seen that the CLEAR routine at $1EAC doesn't call the RESTORE routine at $1E42, so it's never going to do it.


**********************************************************************


** PUTRAM - Move RAMTOP without side effects
   =========================================

PUTRAM allows RAMTOP to be moved anywhere (including anywhere stupid) from within a BASIC program while preserving the m/c stack and GO SUB stack and BASIC variables; it also updates (ERR_SP). I use it to change the space available for BASIC or m/code while a program is running. There's a related UNDEFINE program which reclaims space in VARS as well (see Variables.TXT).

PutRAM.ASM [code]
;Change RAMTOP to "val" while preserving variables & GOSUBs
;RANDOMIZE val+USR putram
        org  48000
PUTRAM: di
        ld   hl,$0000
        add  hl,sp           ;HL=stack pointer
        ld   de,(23730)      ;(RAMTOP) = old RAMTOP
        ex   de,hl
        and  a
        sbc  hl,de           ;(RAMTOP)-SP
        ld   b,0
        ld   c,l
        inc  bc              ;BC=number of bytes to copy
        add  hl,de
        ld   ix,(23653)      ;STKEND
        ld   d,(ix-2)
        ld   e,(ix-3)        ;DE=new RAMTOP
        ld   (23730),de
        push hl
        sbc  hl,de           ;old - new
        pop  hl
        jr   c,COPYON
        dec  bc              ;old > new
        sbc  hl,bc
        ex   de,hl
        sbc  hl,bc
        ex   de,hl
        inc  bc
        ldir
        jr   COPYDO
COPYON: lddr                 ;old < new
        inc  de
COPYDO: sbc  hl,de           ;HL=displacement
        ld   de,(23613)      ;(ERR_SP)
        ex   de,hl
        and  a
        sbc  hl,de
        ld   (23613),hl      ;move (ERRSP)
        ld   hl,$0000
        add  hl,sp
        sbc  hl,de
        ld   sp,hl           ;move SP
        ei
        ret
[/code]


**********************************************************************