
**********************************************************************
files: Variables.TXT, Variables.TAP
       FindVARS.BAS, Indirect.BAS, Indirect.ASM, Undefine.ASM
       DEFADD_VARS1.BAS, DEFADD_VARS2.BAS
       for DumpVARS:
http://www.users.globalnet.co.uk/~jg27paw4/pourri/dumpvars.bas
http://www.users.globalnet.co.uk/~jg27paw4/pourri/dumpvars.asm
http://www.users.globalnet.co.uk/~jg27paw4/pourri/scrdol0.asm
http://www.users.globalnet.co.uk/~jg27paw4/pourri/printa0.asm
http://www.users.globalnet.co.uk/~jg27paw4/pourri/adjust.asm
**********************************************************************


** Finding a Variable in VARS
   ==========================
FindVARS
The Spectrum data structures are explained on pages 122-124 of the 48k BASIC Programming manual. Here's an example of a routine which searches the VARS area for a requested variable. It lists all the variables which it finds up to the named one.

FindVARS.BAS [code]
3000 RESTORE 3000: DIM v$(7,20):
     FOR a=1 TO 7: READ v$(a): NEXT a:
     DATA "","string","number (short name)","number array",
          "number (long name)","string array","FOR...NEXT control"
3001 CLS : PRINT INVERSE 1;"SEARCH FOR VARIABLE"; INVERSE 0;": of Type:"':
     FOR a=2 TO 7: PRINT a;" = ";v$(a): NEXT a:
     INPUT "Type ";o,"Name ";o$: LET o=FN i(o,2,7): GO TO FN c()+(o>0)
3002 LET cd=o*32+CODE o$-96: LET f=0:
     FOR i=FN p(23627) TO FN p(23641)-2:
       PRINT " ";: LET d=PEEK i:
         IF d=cd THEN LET f=i
3003   LET e=INT (d/32): LET l$=CHR$ (d-e*32+96): PRINT l$;
3004   IF e=2 OR e=4 OR e=6 THEN
         LET i=i+2+FN p(i+1): PRINT "$" AND e<>4;"()" AND e<>2;
3005   IF e=3 THEN
         LET i=i+5
3006   IF e=7 THEN
         LET i=i+18: PRINT "...";
3007   IF e=5 THEN
         LET f=0:
         FOR a=i+1 TO FN p(23641)-2:
           IF PEEK a<128 THEN
             LET u=PEEK a: LET l$=l$+CHR$ u: PRINT CHR$ u;:
             NEXT a
3008   IF e=5 THEN
         LET i=a+5: LET u=PEEK a-128: LET l$=l$+CHR$ u: PRINT CHR$ u;:
         IF l$=o$ THEN
           LET f=i-4-LEN o$
3009   IF f THEN
         PRINT '"found at ";f: RETURN
3010 NEXT i: PRINT '"NOT FOUND": RETURN
3099 DEF FN c()=FN p(23621):
     DEF FN i(n,a,z)=INT ABS n AND n>=a AND n<=z:
     DEF FN p(a)=PEEK a+256*PEEK (a+1)
[code]


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


** Indirect Variable Assignment
   ============================

This routine allows assignment of values to variables where the source & destination variables can themselves be defined in variables (or expressions which evaluate to variable names). Some of this can already be done with VAL or VAL$, but this works with both numbers & strings and both the source & destination can be assigned indirectly.

So, for example, variable names can be constructed at runtime, and assignments can be made in a loop to arbitrary variables which aren't in an array, like this:

Indirect.BAS [code]
   5 LET id=25000: DATA "cat",7,"dog",22,"rabbit",3
   6 FOR a=1 TO 3: READ v$,v
  10   PRINT USR id,"val"+CHR$ (a+64),"=",v;
  11   PRINT USR id,v$,"=",v+VAL ("val"+CHR$ (a+64));
  20 NEXT a
  30 PRINT valA'valB'valC
  31 PRINT cat'dog'rabbit
[/code]

This effectively allows any arbitrary group of variables to be processed as though they were in an array, or in different arrays as though they were in the same array, by applying the same operations to them in a loop managed by a DATA statement. The values in the DATA statement could themselves be variables, increasing the possibilities for flexibility (and confusion).

The syntax is:
;PRINT USR indirect,arg1,op,arg2;
;op indicates which args are indirect assignments:
;   "<" = left, "<>" (SymSh-W) = both, ">" = right, "=" = neither

There's more explanation & examples in the listing. Note the use of "" to protect arguments from being evaluated in advance by the BASIC interpreter; choosing whether or not to quote an argument affects the level of indirection. It takes a while to get the hang of all the possible combinations and their effects. Getting it wrong will give "Undefined variable" or "Invalid argument" or "Parameter error" or "Nonsense in BASIC".

It works on 48k, 128k and +3 models. It could probably be made shorter; there's rather more fiddling about with flag bits than I would like. The ASSIGN segment at the end could be used on its own for assigning values to BASIC variables from machine code. Just put the value on the calculator stack and point CH_ADD to the name and "call CLASS_01: call LET".

Indirect.ASM [code]
SCANNING: equ  $24fb         ;the SCANNING subroutine
FETCHS: equ  $2bf1           ;from calculator stack: AEDCB
CH_ADD: equ  23645
MEMBOT: equ  23698

;Indirect Variable Assignment
;PRINT USR indirect,arg1,op,arg2;
;op indicates which args are indirect assignments:
;   "<" = left, "<>" (SymSh-W) = both, ">" = right, "=" = neither
;If arg is indirect then it must be a string variable or expression
;evaluating to a (number or string) variable name.
;The two args must resolve to values of the same data type (number or string).

;eg: "a$","<",b(c,d);
;    assigns the variable named in a$ the number contained in b(c,d)
;eg: "a$","<",b$(c,d);
;    assigns the variable named in a$ the string contained in b$(c,d)
;eg: "a$",">","b(c,d)";
;    gives an error as b(c,d) cannot contain a variable name
;eg: "b(c,d)",">","a$";
;    assigns b(c,d) the number contained in the variable named in a$
;eg: "b$(c,d)",">","a$";
;    assigns b$(c,d) the string contained in the variable named in a$
;eg: "b(c,d)","<","a$";
;    gives an error as b(c,d) cannot contain a variable name
;eg: "b$(c,d)","<>","a$";
;    assigns the variable named in b$(c,d) the value of the variable named in a$

INDIRECT:
        ld   hl,14
        add  hl,sp
        ld   sp,hl           ;undo USR
        ld   ix,0
GETARG: rst  $20
        call SCANNING
        ld   a,(iy+1)
        push af              ;save (FLAGS) for each arg
        inc  ix
        rst  $18
        cp   ","
        jr   z,GETARG
        push ix
        pop  bc
        ld   a,c             ;A =number of args
        cp   3
        jp   nz,$288b        ;"Q Parameter error" (wrong number of args)

        ld   ix,(CH_ADD)
        rst  $28
        defb $01,$38         ;swap op.arg to top of calc.stack

        pop  bc
        pop  af              ;A =(FLAGS)
        push bc              ;also swap AF on machine stack
        bit  6,a
        jp   nz,$288b        ;"Q Parameter error" (if numeric)

        call FETCHS
        ld   a,(de)          ;A =op.arg char: "<" ">" "<>"
        res  6,(iy+55)       ;use bit 6 in FLAGX  for loop control
        res  5,(iy+48)       ;use bit 5 in FLAGS2 for loop control
        set  6,(iy+48)       ;use bits 6&7 in FLAGS2 for LH&RH
        set  7,(iy+48)       ;to indicate arg indirection
        cp   $c9             ;="<>"?
        jr   z,OPDONE
        cp   "<"
        jr   z,OPLEFT
        res  6,(iy+48)
        cp   ">"
        jr   z,OPDONE
OPLEFT: res  7,(iy+48)
        ;treat any other op.arg as meaning "no indirection"

        ;check RH (source) argument
OPDONE: res  6,(iy+1)        ;reset type in (FLAGS)
        pop  af              ;A =(FLAGS) for this arg
        bit  6,a
        jr   z,STRING        ;if string
        bit  5,(iy+48)       ;if numeric, check if LH/RH arg
        jr   z,SWAPLR        ;RH arg may be a number
        jp   $34e7           ;"A Invalid argument"
                             ;(LH arg must name a destination variable)
STRING: bit  7,(iy+48)
        jr   z,SWAPLR        ;no indirection
GETVAL: call FETCHS
        push de
        inc  bc
        rst  $30             ;BC_SPACES
        pop  hl
        ld   (CH_ADD),de
        push de
        ldir
        ex   de,hl
        push hl
        dec  hl
        ld   (hl),$0d        ;add a terminator
        call SCANNING        ;evaluate that name (or expression)
        pop  hl
        pop  de
        call $19e5           ;RECLAIM_1
        bit  6,(iy+55)
        jr   nz,SWAPLR
        set  6,(iy+55)
        jr   GETVAL          ;back to evaluate named RH variable

SWAPLR: bit  5,(iy+48)       ;both args checked?
        jr   nz,ASSIGN       ;jump to assignment if so
        set  5,(iy+48)       ;set processing flag for LH arg
        set  6,(iy+55)
        res  7,(iy+48)
        bit  6,(iy+48)
        jr   z,NOTIND
        set  7,(iy+48)       ;set indirection flag for LH arg
NOTIND: rst  $28
        defb $01,$38         ;swap LH,RH to RH,LH on calc.stack
        jr   OPDONE          ;now check LH (destination) argument

;now on calc.stack: source value, destination name
ASSIGN: bit  6,(iy+1)        ;check type of destination name
        jp   nz,$288b        ;"Q Parameter error" (if numeric)
        call FETCHS          ;get parameters for destination name
        ex   de,hl
        ld   de,MEMBOT       ;copy name to workspace
        push de
        ldir
        ex   de,hl
        ld   (hl),$0d        ;add a terminator
        pop  de
        ld   (CH_ADD),de
        call $1c1f           ;CLASS_01
        call $2aff           ;LET

        ld   (CH_ADD),ix
        ret
[/code]


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

** Undefine function
   =================

Here's a routine to selectively remove variables from the VARS area and reclaim the space. This can be done partially from BASIC already by assigning a null value to a string or re-DIMing an array to minimum size, but this routine works with simple numbers and FOR...NEXT controls as well and removes them completely. Although at 92 bytes, unless there's a lot of variables to undefine, or numbers with long names, or FOR...NEXT controls, it's usually going to be more compact to just use the sloppy BASIC method, so it may only be of academic interest.

It does have another advantage in that the arguments can themselves be variables, so the same command line could be used to undefine different variables, using a DATA...READ loop or taking the names from an array, for example.

It won't work as a direct command, because a direct command is held in the E_LINE area (above VARS) rather than in the BASIC area, so catering for that as well would entail adjusting the next address pointer by the reclaimed amount as each name was fetched off the calc.stack, as the command line would move down each time. As I couldn't see a good reason for running it as a direct command I didn't include the extra code for that.

Any number of explicit variable names can be specified on the command line, each enclosed in "", separated by "," and terminated with a ";"; or implicitly as variables; eg.:

[code]
PRINT USR undefine,"a","bbb","c()","d$","e$()";
-or-
DATA "a","bbb","c()","d$","e$()"
FOR z=first TO last: READ z$: PRINT USR undefine,z$;: NEXT z
-or-
FOR z=first TO last: PRINT USR undefine,z$(z);: NEXT z
[/code]

Variable names which are already undefined are just ignored.

Undefine.ASM [code]
RECLAIM2: equ  $19e8         ;ROM routine RECLAIM_2
SCANNING: equ  $24fb         ;ROM routine SCANNING
LOOKVARS: equ  $28b2         ;ROM routine LOOK_VARS
FETCHS:   equ  $2bf1         ;calculator stack to AEDCB
E_LINE:   equ  23641
CH_ADD:   equ  23645

;stack function parameters passed from BASIC by:
;PRINT USR undefine,"arg1"[,"arg2"...,"argn"];
UNDEF:  ld   hl,14
        add  hl,sp
        ld   sp,hl           ;undo USR
        ld   ix,0
GETARG: rst  $20
        call SCANNING
        inc  ix
        rst  $18
        cp   ","
        jr   z,GETARG
        push ix
        pop  bc
        ld   b,c             ;B =number of parameters
        ld   ix,(CH_ADD)

NXTARG: push bc
        call FETCHS
        ld   (CH_ADD),de     ;variable name address
        push bc              ;variable name length
        call LOOKVARS
        pop  de
        jr   c,CHKNXT        ;variable already undefined
        jr   z,STRARR        ;string or any array

        ;number or for...next control
NUMBER: ld   bc,5            ;length of a number
        and  a
        sbc  hl,de
        inc  hl
        ld   a,(hl)          ;initial letter of name
        and  $E0
        cp   $E0
        jr   nz,NOTFOR       ;not a for...next control
        ld   bc,18           ;length of a for...next control
NOTFOR: ex   de,hl
        add  hl,bc
        ld   b,h
        ld   c,l             ;BC=(length of name)+(length of variable)
        ex   de,hl           ;HL=address of variable
        call RECLAIM2
        jr   CHKNXT

        ;string or any array
STRARR: inc  hl
        ld   c,(hl)
        inc  hl
        ld   b,(hl)          ;BC=length of string or any array
        dec  hl
        dec  hl
        ld   de,3            ;DE=1 (name) + 2 (length field)
        jr   NOTFOR

CHKNXT: pop  bc
        djnz NXTARG
        ld   (CH_ADD),ix
        rst  $18
        ret
[/code]


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


** Using the DEFADD area for extra variables
   =========================================

The UDG area can be used to hold 21 single-letter variables which can be referenced in expressions and assigned values with LET.
DEFADD_VARS1.BAS [code]
  10 DEF FN p(a)=PEEK a+256*PEEK (a+1):
     CLEAR 65367: LET UDG=FN p(23675): LET k0=NOT PI:
     DATA a+65,14,k0,k0,RND*256,k0,k0,CODE ",":
     REM The RND is for test; normally that byte would be zero as well.
  11 FOR a=0 TO 20: LET base=UDG+a*8: RESTORE 10:
       FOR b=base TO base+7: READ c: POKE b,c: NEXT b:
     NEXT a
  12 POKE b-1,CODE ")": RANDOMIZE UDG:
     POKE 23563,PEEK 23670: POKE 23564,PEEK 23671
[/code]

All 21 don't have to be assigned, any number <= 21 can be assigned, using any of the 26 letters [b]but[/b] any that are assigned [b]must not[/b] be used as FOR...NEXT control variables; this is because single-letter variables use less space than FOR...NEXT control variables, and the normal reclaim & recreate mechanism won't work when variables are above STKEND. So in this example only v,w,x,y,z can be used as FOR...NEXT controls.

Note also that these variables will not be cleared by CLEAR or RUN, they will not be saved by SAVE (BASIC) and they can only be referenced from BASIC while the program is running and DEFADD has been set to point to them. To disable them and free up those letters for other use, POKE 23564,0.

DEFADD_VARS1.BAS [code]
  13 REM Just to prove that they exist & can be assigned.
  14 RESTORE 16: LET letter=FN p(23639)+6
  15 FOR v=CODE "A" TO CODE "U":
       PRINT CHR$ v;" = ";VAL CHR$ v;TAB 7;: LET add=1/RND:
       PRINT " + ";add;" = ";: POKE letter,v: POKE letter+2,v
  16 LET A=A+add: PRINT VAL CHR$ v:
     NEXT v
  17 REM They can be used in statements & expressionS in the normal way.
  18 PRINT "LET s=s+t+u";TAB 5;"= ";s;TAB 5;"+ ";t;TAB 5;"+ ";u;TAB 5;"= ";:
     LET s=s+t+u: PRINT s
[/code]


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

** DumpVARS (listing variables during runtime)
   ===========================================

The routine is activated by pressing BREAK. It will then save the screen, list the contents of currently defined variables, restore the screen, then continue from the point where BREAK was pressed. Although the print routine itself is written in BASIC it does not interfere with the main program's variables as the VARS pointer is temporarily moved up to protect them.

Part the 1st - the BASIC variables lister BASIC listing:
http://www.users.globalnet.co.uk/~jg27paw4/pourri/dumpvars.bas
NOTES:
* When activated by pressing BREAK this will call MCTRAP to save the full 24*32 screen display (excluding attributes) in 768 bytes instead of 6144. Any screen characters not found in any of the three character sets (CHARS, UDG, BLOCK) will be stored as spaces. The attributes don't need saving as the listing routine doesn't change them (so it's best not have any squares with the same INK & PAPER, as you won't be able to read anything printed in them). The screen is restored after pressing ENTER on the last page of the listing.
* Long strings and names are truncated to 15 characters. Array elements are printed sequentially as they appear in the array. String array elements are scaled to the length of the last dimension (truncated to 15 if necessary).
* It takes 4 seconds to save the screen and (almost) no time at all to restore it.
* Codes $90-$FF may be used for UDGs (112 values). The location of the BLOCK set is held in 23680/1. (The 16 block graphics have their own character set, rather than calling $0b38 PO_GR_1, as it simplifies some of the other routines.)
* The BREAK trap is enabled by enabling MCTRAP with GO SUB 9810. MCTRAP needs to be disabled before attempting to quit the main program, which can be done with either GO SUB 9811 or GO SUB 9812. If a STOP (or any other error report) - other than the STOP at the end of line 9805 - is encountered then MCTRAP will disable itself and quit anyway.
* The main program variables are kept distinct from the listing routine variables by shifting (VARS) to (E_LINE) and (E_LINE) to (E_LINE)+1 (there's always at least one spare byte in E_LINE, even if it's just the terminator). When it's finished then (E_LINE) is restored, $16b0 SET_MIN called (which has the effect of deleting the new variables which had been stored from the old (E_LINE) position), then (VARS) is restored to recover the old variables and the main program is resumed from the statement after the BREAK point.

Part the 2nd - the error trap routine assembler listing:
http://www.users.globalnet.co.uk/~jg27paw4/pourri/dumpvars.asm
NOTES:
* MCTRAP starts by identifying the Spectrum model so that it can set the stack correctly prior to returning to BASIC. It will then do one of these:
*a) If BREAK (report L - BREAK into program) was pressed, call SaveSCR to save the screen and then jump to 9800 to list the main program variables.
*b) If a STOP was executed and the line=9805, call RestSCR to restore the screen and then continue with the next BASIC statement after the BREAK key was pressed.
*c) Anything else, jump to 9811 to disable MCTRAP and quit the main program.
* If the routine is relocated then InitSCR must be called once to adjust any absolute addresses.
* MCTRAP uses Adjust for relocation, ScrDol0 to identify the characters on-screen when saving and PrintA0 to print them when restoring (see below).

Part the 3rd - the improved SCREEN$ routine assembler listing:
http://www.users.globalnet.co.uk/~jg27paw4/pourri/scrdol0.asm
NOTES:
; Fixes ROM stacking error
; Handles regular characters, UDGs & block graphics
; Number of characters of each type is user-definable (see ScrData)
; Handles full 24*32 screen, up to 112 UDGs
* ScrDol0 is written to be called from the function:
  DEF FN s$(r,k)=STR$ USR ScrDol0
  so if called from elsewhere a dummy DEFADD space needs to be assigned to contain r & k and BC=ScrDol0 prior to the call (see DumpVARS.ASM).
* SCRinit must be called once to set up various pointers and calculate the parameters for the three character sets.

Part the 4th - the streamlined print routine assembler listing:
http://www.users.globalnet.co.uk/~jg27paw4/pourri/printa0.asm
NOTES:
* There are three entry points for this routine:
PRINTRO:;test from BASIC (otherwise this first section is not needed)
PRINTA: ;print character A at row B column C with attributes A'
PRINTA0:;print character A at DE
* If the routine is being used independently then SetBLOCK must be called once to locate the BLOCK character set.

Part the 5th - the relocation routine assembler listing:
http://www.users.globalnet.co.uk/~jg27paw4/pourri/adjust.asm
NOTES:
* This allows routines to self-relocate, by adjusting addresses in the target routine which are specified by the setup routine; that is, commands such as CALL & JP using absolute addresses within the target routine, which would otherwise become invalid if the routine was run from an address other than that at which it was assembled. The InitSCR sections in Adjust.ASM and DumpVARS.ASM give examples of its use.


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

As well as testing on the 48k, I checked it on the +2A, shifting all the routines above $C000, and they still worked OK (after I'd remembered to link in the print routine (Part the 4th) - things didn't go very well before that), so they should work on any other model as well.

Line 9898 in the demo should read (replacing names with addresses):
[code]
9898 RANDOMIZE USR SCRinit:
     POKE DumpVARS,PEEK 23670: POKE DumpVARS+1,PEEK 23671:
     RANDOMIZE USR DumpVARS+12:
     RANDOMIZE PRINTA0:
     POKE DumpVARS+2,PEEK 23670: POKE DumpVARS+3,PEEK 23671:
     GO SUB 9810
[/code]


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

