Classic Computer Magazine Archive COMPUTE! ISSUE 71 / APRIL 1986 / PAGE 79

IBM Variable Snapshot

Tony Roberts, Production Director

This programming utility lets you list the current values of all active variables in any BASIC program-an invaluable aid for debugging. It works on any IBM PC with BASICA or PCjr with Cartridge BASIC.


When things go haywire with a BASIC program, my first inclination is to check the variables: PRINT A$, PRINT SCORE, PRINT UPPERLIMIT, and so on. Comparing what's actually stored in a variable with what you expected often helps to isolate programming problems.
    Printing variable values over and over, however, quickly becomes tedious, especially when arrays are involved. "IBM Variable Snapshot" takes the work out of this process.
    After temporarily appending the Variable Snapshot utility to your program, you can activate it with a simple GOTO command whenever your program stops with an error or you press the BREAK key. Once activated, Variable Snapshot sifts through memory, printing out first the scalar variables, then the array variables it finds there. Within seconds, you can see the values of all the variables your program has used. This kind of analysis has many benefits:
    • By frequently checking the variable list, you reduce the possibility of "forgotten" variables.
    • You can quickly spot typographical errors in variable names. If the list contains both FILENAME$ and FILENAM$, you'll realize something is wrong.
    • By checking variable types as well as names, you'll notice if the list contains both TOTAL% (an integer variable) and TOTAL! (a single-precision variable)-another common source of errors.

How To Take Snapshots
Type in Program 1 below and save it on disk in ASCII format. If you type it in with the "IBM Automatic Proofreader," published elsewhere in this issue, the program is saved in ASCII format automatically. Otherwise, use the command SAVE "SNAPSHOT.ASC",A.
    Program 2 lets you test Variable Snapshot to verify that it's working properly before using it with your own programs. To run a test, type in Program 2 and save it on disk in ASCII format. Then append Snapshot to it with the command MERGE "SNAPSHOT.ASC." Now type RUN. The test program initializes several variables, then stops. When you type GOTO 1000 (the starting line number of Variable Snapshot), the name and value of each variable is printed on the screen. You can press CTRL-NUM LOCK on the PC or Fn-Q on the PCjr to pause the display, or stop it at any time by pressing CTRL-BREAK on the PC or Fn-BREAK on the PCjr.
    If the variable values are not what you expected, recheck your typing, paying close attention to the type declaration symbols (%, $, !, #) attached to the variables. If even one of these symbols is incorrect or missing, you'll have problems.
    The test program initializes integer, string, single-precision, and double-precision variables as well as a full set of array variables. If everything prints out as expected, you can be pretty sure that Variable Snapshot is working well.

Friendly Filename And Quick Start
When Snapshot begins its work, the first thing it prints is the active disk filename, which the IBM stores in the 11 memory locations beginning at 4F1h (1265 decimal). This has nothing at all to do with variables, but simply provides an answer to the question "What did I call this program the last time I saved it?"
    If you want to get started with Snapshot quickly, you can omit the entire array processing section (lines 1590-2220) and change line 1280 to read:

1280 IF QARRAYON% THEN END

    This abbreviated version of Snapshot lists only simple variables, but you can go back later and add the lines to handle the array variables. The REMs in the program listing are not referenced by other lines, so you can safely omit them when typing the program.
    After you have Snapshot working, edit line 1000 to suit your preferences for screen color, width, and so on.
    You may want to renumber Snapshot so its line numbers won't interfere with those of your own programs. (Low line numbers were used in the listing to make entering the program easier.) Load the program into memory and use the command RENUM xxxxx, where xxxxx is Snapshot's new starting line number. Then save the program back to disk, again using the ASCII option so Snapshot can be merged other programs.
    The version I use begins at line 60000, and I've programmed a function key to execute the command GOTO 60000. Whenever a program halts, I simply press Fn-6 to see the value of every variable.


Variable
 
Description
Q%,QQ%,QQQ% loop counters
QTYPE% variable type
QLENLEFT% number of characters left in variable name
QDIMS% number of dimensions in array
QARRAYON% flag indicating if array boundary passed
QSTRLEN% length of string variable
QBASE% status of OPTION BASE command
Q$ for single- and double-precision conversions
QCHAR$ builds active filename
QFILE$ active filename
QNAME$ name of variable being processed
QVAR! memory pointer to current variable
QARRAY! start of array space
QFREE!  start of free space
QASIZE! size of current array
QVALUE! temporary storage for integer values
QSTRPTR! points to location of actual string
QPTR! points to start of next element in array
QDIMSIZE( ) size of array dimensions



Array Bases
IBM BASIC includes the OPTION BASE statement for defining the lowest-numbered element in an array. If a program contains the statement OPTION BASE 0, or if no OPTION BASE statement is included, all arrays start with a 0 element. An OPTION BASE 1 statement means that arrays begin with element 1.
    Variable Snapshot must know which OPTION BASE is in effect to display array values properly. Memory location 45Ch (1116 decimal) provides this information. PEEKing that address yields either a 0 or 1, indicating which base is selected.
    The adjacent memory location, 45Dh (1117 decimal), is related but a little more specific. If no OPTION BASE command has been issued, 45Dh contains a 0; if OPTION BASE 0 has been executed, 45Dh contains a 1; and if OPTION BASE 1 has been executed, the location contains a 2.
    Try changing line 10 in Program 2 to read OPTION BASE 0 and observe the effect when running Variable Snapshot.
    Although IBM BASIC allows arrays of up to 255 dimensions, few programs make use of more than one or two. For this reason, Variable Snapshot does not include provisions for arrays with more than two dimensions. Additional loops can be added to handle more complex arrays, if necessary.

A Few Cautions
To be truthful, Snapshot does not list every variable-it ignores those that begin with the letter Q. The Snapshot routine itself, you'll notice, uses only variables beginning with the letter Q. That keeps Snapshot's own variables from being printed along with those of your program.
    If you're inclined to tinker with this routine, you must be careful about introducing new variables. Lines 1020-1040 initialize every variable used by the routine, effectively reserving space for them in the variable table.
    Lines 1120-1140 determine the boundaries of the variable table, reference points the program can not do without. If a new variable is added to the program after the boundary measurements are taken, confusion results; the boundaries move and Snapshot loses its way.
    Although Snapshot works with most programs, there can be complications. If you've written your program to make use of all available memory, there won't be room in the variable table for Snapshot's own variables. You'll need to leave Snapshot about 300 bytes of workspace.


Byte 1 = type (2 = integer, 3 = string, 4 = single precision, S = double precision)
Byte 2 = first character of variable name
Byte 3 = second character of variable name
Byte 4 = number of characters remaining in variable name
Byte 5
     .
     . =  rest of variable name (high bit set)
     .


How Snapshot Works
As mentioned above, Snapshot reads the boundaries of the scalar variable area, the array variable area, and the free space area, then works its way through the variable areas byte by byte deciphering the information stored there. Once it reaches free space, its work is finished.
    The IBM stores scalar variables as shown below.
    Following the last character of the variable name is the value of the variable.
    • An integer variable is stored in two bytes in the standard low byte/high byte format. The high bit of the second byte indicates the sign of the integer. If it is set, the integer is a negative number.
    • String variable pointers are stored in three bytes. The first is the number of bytes in the string, and the second and third point to the address (either in the string pool or in the BASIC program area) where the string is stored.
    • Single-precision variable values are stored in four bytes. The values of these bytes can be concatenated into a string, then converted into a single-precision number using the CVS function.
    • Double-precision variable occupy eight bytes, which can be concatenated and converted as above using the CVD function.
    Array variables are stored similarly, but there's some additional information between the end of the variable name and the actual beginning of the variable values.
    Following the variable name are two bytes that indicate the total size of the array. The next byte holds the number of dimensions That is followed by two bytes describing the number of elements it the last dimension. Then two byte describe the number of elements it the next to last dimension, and so on, until each dimension in the array has been defined.
    Finally, the values of the array variables follow, and are stored it the same manner as values for scalar variables.
    Using this information, the program listing, the description of Snapshot variables found in the accompanying table, and the actual program output, you should be able to develop a good understanding of how BASIC treats your variables.

For instructions on entering these listings, please refer to "COMPUTE!'s Guide to Typing In Programs" in this issue of COMPUTE!.

Program 1: IBM Variable Snapshot

CK 1000 DEF SEG:SCREEN 0,0:WIDTH 80:CO
        LOR 7,0:
DG 1010 REM initialize variables
CG 1020 Q%=0:QQ%=0:QQQ%=0:QLENLEFT%=0:
        QTYPE%=O:QDIMS%=0:QARRAYON%=0:
        QSTRLEN%=0:QBASE%=0:QDIMSIZE%(
        1)=0:QDIMSIZE(2)=0
LN 1030 Q$="":QCHAR$="":(QFILES="":QNAM
         E$=""
MJ 1040 QVAR!=0:QARRAY!=0:OFREE!=0:QAS
        IZE!=O:QVALUE!=0:GSTRPTR!=0:GP
        TR!=0
GG 1050 REM Get active filename
OL 1060 FOR Q%=0 TO 10
BA 1070 QCHAR$=CHR$(PEEK(&H4F1+Q%))
PA 1080 IF ASC(QCHAR$)>96 AND ASC(QCHA
        R$)<123 THEN QFILE$=QFILE$+CHR
        $(ASC(QCHAR$)-32) ELSE QFILE$=
        QFILE$+QCHAR$
AH 1090 NEXT
LI 1100 PRINT:PRINT "Active disk filen
        ame is:  ";MID$(QFILE$,1,8);".
        ";MID$(QFILE$,9):PRINT
CA 1110 REM get addresses of scalar va
        riables, array variables, and
        free space
FN 1120 QVAR!=PEEK(&H358)+PEEK(&H359)*
        256
HI 1130 QARRAY!=PEEK(&H35A)+PEEK(&H35B
        )*256
HD 1140 QFREE!=PEEK(&H35C)+PEEK(&H35D)
        *256
HF 1150 QBASE%=PEEK(&H45C)
ON 1160 REM Start of variable processi
        ng
CH 1170 QTYPE%=PEEK(QVAR!)
JO 1180 IF (QTYPE%<2 OR QTYPE%>4) AND
        QTYPE%<>B THEN END
L6 1190 QLENLEFT%=PEEK(QVAR!+3)
PH 1200 REM get variable name
ON 1210 QNAME$=""
LL 1220 IF PEEK(QVAR!+l)>127 OR (PEEK(
        QVAR!+l)=81 AND QARRAYON%=0) T
        HEN 2240
EC 1230 FOR Q%=1 TO QLENLEFT%
HJ 1240 QNAME$=QNAME$+CHR$(PEEK(QVAR!+
        3+Q%) AND 127)
BP 1250 NEXT
NE 1260 QNAME$=CHR$(PEEK(QVAR!+l))+CHR
        $(PEEK(OVAR!+2))+QNAME$
OI 1270 REM branch to appropriate rout
        ine depending on variable type
E6 1280 IF QARRAYON% THEN 1600
CF 1290 ON QTYPE%=1 GOTO 1320, 1370, 146
        0
NP 1300 GOTO 1530
FN 1310 REM inteters
EC 1320 QVALUE!=PEEK(QVAR!+QLENLEFT%+4
        )+PEEK(QVAR!+QLENLEFT%+5)*256
OP 1330 IF QVALUE!>32768! THEN QVALUE!
        =QVALUE!-65536!
PH 1340 PRINT QNAME$;"%";,"= ";QVALUE!
NC 1350 GOTO 2240
FE 1360 REM strings
PH 1370 PRINT QNAME$;"$","=  ";CHR$(34
        );
ON 1380 QSTRLEN%=PEEK(QVAR!+QLENLEFT%+
        4)
NC 1390 QSTRPTR!=PEEK(QVAR!+OLENLEFT%+
        5)+PEEK(OVAR!+QLENLEFT%+6)*256
6J 1400 FOR Q%=0 TO QSTRLEN%-1
PM 1410 PRINT CHR$(PEEK(QSTRPTR!+Q%));
DK 1420 NEXT
6I 1430 PRINT CHR$(34)
NO 1440 SOTO 2240
PJ 1450 REM single precision
PL 1460 Q$=""
FG 1470 PRINT QNAME$;"!","= ";
CM 1480 FOR Q%=0 TO 3: Q$=Q$+CHR$(PEEK
        (QVAR!+QLENLEFT%+4+Q%))
BP 1490 NEXT
BB 1500 PRINT CVS((Q$)
NK 1510 GOTO 2240
KB 1520 REM double precision
OE 1530 Q$=""
HH 1540 PRINT QNAME$;"#","= ";
6N 1550 FOR Q%=0 TO 7: Q$=Q$+CHR$(PEEK
        (QVAR!+QLENLEFT%+4+Q%))
BI 1560 NEXT
FE 1570 PRINT CVD(Q$)
OP 1580 SOTO 2240
AC 1590 REM array routines
JM 1600 QASIZE!=PEEK(QVAR!+4+QLENLEFT%
        )+PEEK(QVAR!+5+QLENLEFT%)*256
JI 1610 IF ASC(QNAME$)=81 THEN 2240
BA 1620 QDIMS%=PEEK((QVAR!+6+QLENLEFT%)
ID 1630 IF QDIMS%>2 THEN 2240
DL 1640 QPTR!=QVAR!+7+QLENLEFT%
NA 1650 FOR Q%=QDIMS% TO 1 STEP -1
LM 1660 QDIMSIZE%(Q%)=PEEK(QPTR!)+PEEK
        (QPTR!+1)*256
NE 1670 QPTR!=QPTR!+2
BA 1680 NEXT
JG 1690 ON QTYPE%-1 GOTO 1720,1830,198
        0
KI 1700 GOTO 2110
DL 1710 REM integer arrays
AB 1720 PRINT
BL 1730 IF QDIMS%=2 THEN FOR QQQ%=QBAS
        E% TO QDIMSIZE%(2)+(QBASE%=0)
61 1740 FOR Q%=QBASE% TO QDIMSIZE%(1)+
        (QBASE%=0 )
DO 1750 QVALUE!=PEEK(QPTR!)+PEEK(QPTR!
        +1)*256
AA 1760 IF QVALUE!>32768! THEN QVALUE!
        =QVALUE!=65536!
IF 1770 IF QDIMS%=1 THEN PRINT QNAME$;
        "%(";MID$(STR$(Q%),2);")","= "
        ;QVALUE! ELSE PRINT QNAME$;"%(
        ";MID$(STR$(Q%),2);",";MID$(ST
        R$(QQQ%),2);")","= ";QVALUE!
Of 1780 QPTR!=QPTR!+2
JM 1790 NEXT 0%
EH 1800 IF QDIMS%=2 THEN NEXT QQQ%
NA 1810 GOTO 2240
HA 1820 REM string arrays
AG 1830 PRINT
BA 1840 IF QDIMS%=2 THEN FOR QQQ%=QBAS
        E% TO QDIMSIZE%(2)+(QBASE%=0)
6N 1850 FOR Q%=QBASE% TO QDIMSIZE%(1)+
        (QBASE%=0)
IE 1860 QSTRLEN%=PEEK(QPTR!)
KA 1870 QSTRPTR!=PEEK(QPTR!+1)+PEEK(QP
        TR!+2)*256
CD 1880 IF QDIMS%=1 THEN PRINT QNAME$;
        "$(";MID$(STR$(Q%),2);")","= "
        ;CHR$(34); ELSE PRINT QNAME$;"
        $(";MID$(STR$(Q%),2);",";MID$(
        STR$(QQQ%),2);")","= ";CHR$(34
        );
CI 1890 FOR QQ&=0 TO QSTRLEN%-1
NF 1900 PRINT CHR$(PEEK(QSTRPTR!+QQ%))
        ;
HJ 1910 NEXT QQ%
6P 1920 PRINT CHR$(34)
OA 1930 QPTR!=QPTR!+3
IB 1940 NEXT Q%
BI 1950 IF QDIMS%=2 THEN NEXT QQQ%
DB 1960 GOTO 2240
EL 1970 REM single precision arrays
ON 1980 PRINT
CB 1990 IF QDIMS%=2 THEN FOR QQQ%=QBAS
        E% TO QDIMSIZE%(2)+(QBASE%=0)
EP 2000 FOR Q%=QBASE% TO QDIMSIZE%(1)+
        (QBASE%=0 )
OF 2010 Q$=""
IH 2020 FOR QQ%=0 TO 3
CB 2030 Q$=Q$+CHR$(PEEK(QPTR!+QQ%))
60 2040 NEXT QQ%
AN 2650 IF QDIMS%=1 THEN PRINT QNAME$;
        "!(";MID$(STR$(Q%),2);")","= "
        ;CVS(Q$) ELSE PRINT QNAME$;"!(
        ";MID$(STR$(Q%),2);","MID$(STR
        $(QQQ%),2);")","= ";CVS(Q$)
PK 2060 QPTR!=QPTR!+4
IJ 2070 NEXT Q%
FA 2080 IF QDIMS%=2 THEN NEXT QQQ%
Of 2090 GOTO 2240
NO 2100 REM double precision arrays
PD 2110 PRINT
DN 2120 IF QDIMS%=2 THEN FOR QQQ%=QBAS
        E% TO QDIMSIZE%(2)+(QBASEX=0)
FK 2130 FOR QX=QBASE% TO QDIMSIZE%(1)+
        (QBASE%=0)
OA 2140 Q$=""
OO 2150 FOR QQ%=0 TO 7
DM 2160 Q$=Q$+CHR$(PEEK(QPTR!+QQ%))
HM 2170 NEXT QQ%
CJ 2180 IF QDIMS%=1 THEN PRINT QNAME$;
        "#(";MID$(STR$(Q%),2);")","= "
        ;CVD(Q$) ELSE PRINT QNAME$;"#(
        ";MID$(STR$(Q%),2);",";MID$(ST
        R$(QQQ%),2);")","= ";CVD(Q$)
EN 2190 QPTR!=QPTR!+8
HI 2200 NEXT Q%
EP 2210 IF QDIMS%=2 THEN NEXT QQQ%
NI 2220 GOTO 2240
GH 2230 REM Bet address of next variab
        le
AM 2240 IF QARRAYON%<>1 THEN QVAR!=QVA
        R!+QLENLEFT%+QTYPE%+4 ELSE QVA
        R!=QVAR!+QASIZE!+QLENLEFT%+6
HH 2250 IF QVAR!=>QARRAY! THEN QARRAYO
        N%=1
NG 2260 IF QVAR!=>QFREE! THEN END
OH 2270 GOTO 1170


Program 2: Snapshot Demo

BL 10 REM Snapshot demo program
DK 20 OPTION BASE 1
LF 30 A%=2:A$="This is a string.":A!=1
      00001!:A#=345692811#
LA 40 DIM INTEGER%(5),STRIN$(5),SINGLE
      !(5),DOUBLE(1(5)
GM 50 DIM IGR%(5,3),STN$(5,3),SNG!(5,3
      ),DBL#(5,3)
IJ 60 FOR I=1 TO 5:INTEGER%(I)=I:STRIN
      $(I)=CHR$(64+I):SINGLE!(I)=I*300
      00:DOUBLE#(I)=I*1,5E+07:NEXT I
MG 70 FOR 1=1 TO 5:FOR J=1 TO 3:IGR%(I
      ,J)=I+J:STN$(I,J)=CHR$(I+64)+CHR
      $(J+48):SNG!(I,J)=100*I*J:DBL#(I
      ,J)=I/J:NEXT J,I
DK 80 END