
One of the many powers lurking within the QL is the ease of extending SuperBASIC. The simplest means of doing this is by defining PROCedures or FuNctions within a SuperBASIC program. The newly defined PROCedure or FuNction is then treated as a SuperBASIC keyword (i.e., PRINT, MODE, SAVE, CODE, etc.). A more sophisticated and global approach is to “link” the new PROCedure or FuNction to the system’s resident memory. In other words, create a semi-permanent extension to SuperBASIC. With this method, any linked keyword is available no matter what program is residing in the computer. As long as another PROCedure or FuNction is not DEFined with an identical name and the computer is not reset, these keywords are available for use by any program, or by direct command.
Let’s take a few seconds and review the difference between a PROCedure and a FuNction. Simply put, a PROCedure will preform a task and a FuNction will return a value or alter a variable. (EXAMPLE: CLS is a PROCedure and INKEY$ is a FuNction.) These guidelines are not strictly followed by QDOS however. It is even possible to redefine certain SuperBASIC keywords. The most recent DEFinition takes precedent over earlier definitions. There are, however, certain keywords which QDOS will not redefine:
END, FOR, IF, REPeat, SELect, WHEN ERRor, DEFine, PROCedue, FuNction, GO TO, GO SUB, WHEN EOF, INPUT, RESTORE, NEXT, EXIT, ELSE, ON, RETurn, REMAINDER, DATA, DIM, LOCal, LET, THEN, STEP, REMark, and MISTake
If one is writing a SuperBASIC program in its proper fashion (i.e., modules of PROCedures and FuNctions), it can be very easy to lose one’s bearings. Since a call to a PROCedure or FuNction is not indexed via a Line #. as in most BASICs, it would be helpful to have a routine to provide a “map” of the SuperBASIC program. While we are at it, why not list the variables and arrays? That is precisely what the PROCedure we are going to write will do. VDUMP will provide a list of all variables and arrays, along with PROCedures and FuNctions, including the Line Number where the DEFinition took place. (See Table 1 for an example.)
We will be using some of the lessons learned in past issues of TIME DESIGNS (March/April & May/June ’88; “Mandelbrot–A Fractal World” parts III & IV), specifically, the areas involving the System Variables BV_NTBAS and BV_NLBAS (Name Table and Name List) and the areas of memory they point to. Our PROCedure will scan through these areas of memory and report on what it finds.
Let’s examine the means for linking in PROCedures and FuNctions. QDOS provides us with a system utility to do just that, BP_INIT add PROCedures and FuNctions to SuperBASIC (vector $110). Upon entry the following registers must be set accordingly:
A1 start of PROCedure/FuNction list
A6 usual SuperBASIC address of SuperBASIC variables
Upon exit the following registers are affected:
D1 preserved A0 preserved
D2 preserved A1 corrupted
D3 corrupted A2 preserved
A3 preserved
A6 may be updated if more
memory was required
Errors: None
The above utility will add the defined PROCedures and FuNctions to the QL’s system. The required format is as follows:
dc.w procnum number of PROCedures to be added
;
; for each PROCedure to be created the following --
;
dc.w proutine-* relative pointer to PROCedure routine
dc.b proclen length of the PROCedure name
dc.b 'proc_name' name of the PROCedure in ASCII
;
; then at the end of the PROCedure list --
;
dc.w 0
dc.w funcnum number of FuNctions to be added
;
; for each FuNction to be created the following --
;
dc.w froutine-* relative pointer to FuNction routine
dc.b fnlen length of the FuNction name
dc.b 'fun_name' name of the FuNction in ASCII
;
; then at the end of the FuNction list --
;
dc.w 0
Be advised if your PROCedure/FuNction names are long (with an average of over 8 bytes), the values for “procnum” and “funcnum” should be calculated using the following formula:
(number of PROCedures + number of FuNctions + number of bytes used in all names + 7)18
Any errors in the above format will more than likely cause a serious system crash. It is important that this format be followed closely. Even if you are only creating PROCedures (as in our example), the information for FuNctions must be included, especially the 0’s following the list of both the PROCedures and FuNctions. (See Listing 2 – label “procs”.)
We are now ready to add a semi-resident PROCedure to QDOS. (Ask anyone with an IBM or clone if they can pull off such a trick as easily as our beloved QL can!)
VDUMP will display (or print) a list of SuperBASIC variables, PROCedures, and FuNctions to any channel the user specifies, providing the channel is open. In order to redirect this information to a channel, other than the default channel (#1), a parameter declaring that channel must be passed to the PROCedure routine. This is accomplished by following the same format as other SuperBASIC commands (i.e., “#” followed by the number of the channel). QDOS provides us with another of its wonderful system utilities to help us.
vardump
Whenever a PROCedure or FuNction is called, any parameters following the name are placed on the RI stack. The first parameter is on the bottom of the stack and pointed to by 0(a6,a1.l). Since the argument separators are lost when one of the following utilities is called, they must be checked or saved before the parameter is retrieved (see the first two lines at label “vardump”). The separators may include “#”, “;”, “,”, “”, etc. The system utility vectors that can be used to retrieve parameters from the maths/RI stack are:
CA_GTINT -- $112 retrieve word integers
CA_GTFP -- $114 retrieve floating point numbers
CA_GTSTR -- $116 retrieve strings
CA_GTLIN -- $118 retrieve long integers
Upon Entry the following registers should be set accordingly:
a1.l top of maths stack
a3.l pointer to first parameter
a4.l pointer to last parameter
Upon Exit the following registers are affected:
d1 corrupted a0 corrupted
d2 corrupted a1 updated pointer top of stack
d3.w no. of parameters a2 corrupted
d4 corrupted a3 preserved
d6 corrupted a4 preserved
Errors: a5 preserved
-15 bad parameter
-17 error in expression
After we save the parameter separator, we use CA_GTINT to retrieve any possible parameter (i.e., “VDUMP #3”). If there are no errors and only one parameter was found, a check is carried out to see if parameter separator was “#” (see “assign”). A check is then carried out to verify that such a channel is open and the internal channel ID is stored in a0. If no parameter is found, the default channel #1 is assigned (“default”). (NOTE: Please refer to the discussion of channel ID’s in the “Mandelbrot” Part III article; Plotter Source Code section “ink”.) The lines in the VDUMP code labeled “assign, starting with “move.l BV_CHBAS(a6),a2” through label “no_chan”, is the method of ascertaining the correct internal channel ID number based on the SuperBASIC channel ID.
The rest of the PROCedure is rather simple and straightforward. Most of the work performed by this PROCedure is similar to our machine code routine in the Mandelbrot program that searched the SuperBASIC variables area for the two variables assigned in BASIC. (See text and source code labeled “calc” along with Table 1 – Variable Types.)
Let’s now examine the various system utilities used by this PROCedure to print the information and messages. The first vector used is UT_MTEXT – $d0 – print message on channel. Upon entry, the following registers must be set:
a0.l channel ID
a1.l start of message
Upon exit the following registers are affected:
d1 corrupted a0 preserved
d2 corrupted a1 corrupted
d3 corrupted a2 preserved
a3 preserved
Errors: Any error from I/O operations (see pages 19-20 of the Concepts section of the QL User’s Guide for a list of Error Codes).
The message pointed to by a1 is sent to the specified channel. The correct format of the message pointed to by a1 is:
dc.w mess_len length of message -- must be word length
dc.b 'message'
It is wise to ensure that the length of the message definition block end on a word boundary, since the CPU expects all addresses to begin on even addresses. (See Listing 2, label “line_mess”.)
Another means of printing to a channel is by using the Trap #3 utility, IO_SSTRG – $07 – send sequence of bytes to channel. Upon entry the following registers must be set accordingly:
d2.w number of bytes to send
d3.w timeout
a0.l channel ID
a1.l location of first byte to send
Upon exit the following registers are affected:
d1.w no. of bytes sent a0 preserved
d2 preserved a1 1 past last byte sent
d3 preserved a2 preserved
a3 preserved
Errors:
-1 not complete
-6 invalid channel ID
-11 drive full
This trap will send as many bytes as it can (which are pointed to by a1) during the timeout specified. NOTE: Timeouts are similar to those used by SuperBASIC in such FuNctions as INKEY$ (24) or INKEY$ (-1).
VDUMP also uses another Trap #3 utility to send just one byte to a channel, IO_SBYTE – $05. Upon entry set the following registers:
d1.b byte to be sent
d3.w timeout
a0.l channel ID
Upon exit the following registers are affected:
d1 corrupted a0 preserved
d2 preserved a1 corrupted
d3 preserved a2 preserved
a3 preserved
Errors: same as IO_SSTRG
It is also possible to send a decimal number to a channel by using the utility vector UT_MINT – $ce. Upon entry set the following registers:
d1.w value to print
a0.l channel ID
Upon exit the following registers are affected:
d1 corrupted a0 preserved
d2 corrupted a1 corrupted
d3 corrupted a2 corrupted
a3 corrupted
Errors: all possible I/O errors
Getting VDUMP into the Machine
Carefully key-in Listing 1 into your computer and save to cartridge or disk before running. I have included error checks in an attempt to catch any mistyped numbers which make up the machine code routine. After you have saved the program, simply RUN the SuperBASIC. It will POKE into memory the machine code and save itself to your specified device (i.e., mdv1_, flp1_, etc.). NOTE: The command RESPR will not work if another job besides SuperBASIC is resident in the QL.
Using VDUMP
The easiest way to link-in our new PROCedure is to incorporate Listing 3 into your normal boot program (be sure to change Line numbers so as not to overwrite any lines already in your boot program). Once the QL has been “booted” with this new PROCedure, the simple command of “vdump” will list all variables, arrays, PROCedures or FuNctions to the default channel (#1). By pressing CTRL F5, the display to the screen can be halted. Pressing any key will reactivate the PROCedure. It is possible to send your “vdump” to a file on a disk or cartridge by first opening a file (OPEN_NEW #7, mdv1_dump), and then using the command “vdump #7”. Be sure to close the file when the PROCedure has returned the flashing cursor (CLOSE #7). You can also send a list of your SuperBASiC variables, PROCedures and FuNctions to your printer. First, open the serial port connected to your printer via (OPEN_SER1C, #7). The “c” insures that QDOS linefeeds (ASCII 10) are sent to your printer as carriage returns (ASCII 13). The command “vdump #7” will now send the listing to your printer.
I hope that this PROCedure will be an aid in creating fantastic SuperBASIC programs and will provide another “open door” to exploring the POWER and VERSATILITY of the QL. All other less sophisticated computers BEWARE…we are taking the “Quantum Leap”!
Suggestions
For the more adventurous souls, the next logical step would be to alter the procedure to print out the values of the variables and the dimension data for the arrays. With this added feature to VDUMP (now called XDUMP), writing and debugging SuperBASIC programs will become less of a chore. By placing XDUMP in a program line where you suspect some “bugs”, a complete value listing of all the declared variables can be checked (i.e., 2002 PRINT #5; “LINE #2002”: XDUMP #5).
For those who do not have the time or the inclination, I will provide such embellishments on microdrive cartridge or floppy disk for an extra $2.50 charge. I will also include QUILL files of this article, along with all supporting programs and source code. To receive VDUMP and/or XDUMP (which will display variable values and array dimension information), send a check or money order for $7.50 (+ $2.50 for XDUMP) to: Michael E. Carver, 1016 N.E. Tillamook, Portland, OR 97212. Or $4.00 (+ $2.50 for XDUMP) if you provide your own cartridge or disk. Please specify what format you wish (only microcartridge and 5 1/4″ DS/DD disk available).
Listing 1 — Machine Code Loader for vdump
10 REMark **** Program to Load 'vdump' procedure
20 REMark **** as machine code and save to device
30 REMark ****
40 REMark ****
50 :
60 checking
70 device
80 into_machine
90 to_medium
100 test
110 :
120 DEFine PROCedure into_machine
130 CLS: PRINT "POKEing code into memory. . ."
140 a=RESPR(332)
150 b=a
160 RESTORE 1000
170 REPeat load_loop
180 FOR x=0 TO 20
190 FOR y=1 TO 8
200 READ number
210 POKE_W (b),number
220 b=b+2: IF b>=a+332 THEN EXIT load_loop
230 END FOR y
240 READ number: REMark *** clear check_sum
250 END FOR x
260 EXIT load_loop
270 END REPeat load_loop
280 END DEFine into_machine
290 :
300 DEFine PROCedure device
310 device$="mdv1_"
320 PRINT "Default device ";device$
330 PRINT "Change device to: "
340 INPUT "(PRESS Return to keep old device) ";newdevice$
350 IF newdevice$<>"" THEN device$=newdevice$
360 CLS: PRINT "Will save to ";device$
370 END DEFine device
380 :
390 DEFine PROCedure test
400 CLS: PRINT "Testing new procedure. . ."
410 DIM dummy(20)
420 CALL a
430 vdump
440 END DEFine test
450 :
460 DEFine PROCedure checking
470 CLS: PRINT "Checking DATA statements. . ."
480 RESTORE 1000
490 FOR x=0 TO 20
500 count=0
510 FOR y=1 TO 8
520 READ number: count=count+number
530 END FOR y
540 READ check_sum
550 IF check_sum<>count
560 PRINT "ERROR -- Check line # ";1000+x*10
570 STOP
580 ELSE
590 END FOR x
600 PRINT "DATA checked -- OKAY"
610 END DEFine checking
620 :
630 DEFine PROCedure to_medium
640 PRINT "Ready ";device$;" Press any key to Save. . ."
650 REPeat loop
660 a$=CHR$(ready)
670 IF a$<>"" THEN EXIT loop
680 END REPeat loop
690 SBYTES device$&'vardump_proc',a,332
700 END DEFine to_medium
710 :
720 DEFine FuNction ready
730 LOCal x
740 c=CODE(INKEY$(-1))
750 RETurn c
760 END DEFine ready
770 :
1000 DATA 12408,272,17402,6,20112,20085,1,14,70300
1010 DATA 1366,17493,19792,0,0,0,18938,300,57889
1020 DATA 6326,-18431,12408,274,20112,19072,26236,3139,69136
1030 DATA 1,27950,26372,28913,20085,2068,7,26614,132010
1040 DATA 12342,-26624,21678,88,9326,48,-16132,40,766
1050 DATA -10816,-18962,52,27654,8310,-22528,24586,28922,37218
1060 DATA 20085,8316,1,1,28672,17402,154,13432,88063
1070 DATA 208,20114,14396,2,24882,17402,152,13432,90588
1080 DATA 208,20114,14396,3,24866,17402,148,13432,90569
1090 DATA 208,20114,14396,4,24850,17402,148,13432,90554
1100 DATA 208,20114,14396,5,24834,20085,17390,28,97060
1110 DATA 10769,-9586,17390,32,10833,-9266,17390,24,37586
1120 DATA 10321,-9778,-18412,26370,24628,8781,-11540,2,30372
1130 DATA -19507,28458,5145,30463,28679,20035,3076,4,96353
1140 DATA 27924,17402,90,13432,208,20114,12844,4,92018
1150 DATA 13432,206,20114,29194,30463,28677,20035,20556,162677
1160 DATA -17780,27840,20085,12,2646,24946,26977,25196,109922
1170 DATA 25971,14858,10,2625,29298,24953,29498,8202,135415
1180 DATA 14,2640,21071,17253,25717,29285,29498,8202,133680
1190 DATA 12,2630,30030,25460,26991,28275,14858,9,128265
1200 DATA 8224,19561,28261,8227,8192,0,0,0,72465
Listing 2 — Source Code for Variable Dump PROCedure (vdump)
;
; title 'VDUMP'
; Procedure to Dump Variables, Procedures, and Functions
; produced in SuperBASIC
;
; written by Michael E. Carver 2/8/88
;
; revised 2/15/88
; final revision 6/19/88
;
; CTL F5 will halt display to screen
; To send to printer open ser1c or ser2c to provide
; Carriage Return
;
; register use d5=end of name table
; a5=start of list table
; d6=holds name table data
; d4=type searching for
; a4=pointer in name table
;
; Labels *********************************************
;
LINEFEED equ $0a ASCII code for Line Feed
VARS equ $02 variable types
ARRAY equ $03 array types
PROC equ $04 Procedure types
FUNC equ $05 Function types
;
; The following labels may be supported by your assembler
;
BV_NTP equ $1c offset end of name table
BV_NLBAS equ $20 offset start of list table
BV_NTBAS equ $18 offset start of name table
CH_LENCH equ $28 length of channel block
BV_CHBAS equ $30 channel table
BV_CHP equ $34 end of channel table
BV_RIP equ $58 maths stack pointer
BP_INIT equ $110 add SuperBASIC Procedure
UT_MTEXT equ $d0 print message on channel
IO_SSTRG equ $07 send sequence of bytes to channel
IO_SBYTE equ $05 send byte to channel
UT_MINT equ $ce send decimal number to channel
CA_GTINT equ $112 get parameters as word integer
;
; ****************************************************
;
; Actual start of program as loaded into machine
;
;
install move.w BP_INIT,a0 link in procedure
lea procs,a1
jsr (a0)
rts
procs dc.w 1 number of procs
dc.w vardump-* relative locataion
dc.b 5,'VDUMP' procedure name
dc.w 0 end of procs
dc.w 0 number of functions
dc.w 0 end of functions
;
; routine to check for parameter & type if not "#" report
; parameter error
;
vardump lea par_sep,a4 save parameter separator
move.b 1(a6,a3.l),(a4)
move.w CA_GTINT,a0
jsr (a0)
tst.l d0 any errors?
bne.s exit quit if any
cmp.w #1,d3 one parameter?
blt.s default no parameter
beq.s assign if one, assign channel ID
bad_para moveq #-15,d0 incorrect parameters
rts return with error report
assign btst #7,(a4) was '#' used?
beq.s bad_para
move.w 0(a6,a1.l),d0 get parameter
addq.l #2,BV_RIP(a6) restore maths stack
move.l BV_CHBAS(a6),a2 find start of channel
mulu #CH_LENCH,d0 ... definition block
adda.l d0,a2 absolute address
cmp.l BV_CHP(a6),a2
bge.s no_chan not in table
move.l 0(a6,a2.l),a0 new channel ID
bra.s start
no_chan moveq #-6,d0 'channel not found'
rts
;
;
default move.l #$10001,a0 channel #1
;
;
; list all SuperBASIC variables ***********************
;
;
start moveq #0,d0
lea.l vars_mess,a1
move.w UT_MTEXT,a2
jsr (a2) print "Variables:"+LF
move.w #VARS,d4
bsr.s pointers
;
; list all SuperBASIC arrays **************************
;
lea.l array_mess,a1
move.w UT_MTEXT,a2
jsr (a2) print "Arrays: "+LF
move.w #ARRAY,d4
bsr.s pointers
;
; list all SuperBASIC PROCedures **********************
;
lea.l proc_mess,a1
move.w UT_MTEXT,a2
jsr (a2) print "PROCedures:"+LF
move.w #PROC,d4
bsr.s pointers
;
; list all SuperBASIC FuNctions ***********************
;
lea.l func_mess,a1
move.w UT_MTEXT,a2
jsr (a2) print "FuNctions:"+LF
move.w #FUNC,d4
bsr.s pointers
exit rts done
;
;
; enter with d4 containing type 02=variable
; 03=arrays
; 04=SuperBASIC procedure
; 05=SuperBASIC function
;
;
pointers lea BV_NTP(a6),a1 end of name table
move.l (a1),d5
add.l a6,d5 absolute address
lea BV_NLBAS(a6),a1 start of list table
move.l (a1),a5
add.l a6,a5 absolute address
lea BV_NTBAS(a6),a1 start of name table
move.l (a1),a4
add.l a6,a4 absolute address
;
; check for proper type
; enter: d4 containing high byte of type ID
; a4 pointing to type
;
search cmp.b (a4),d4 check type
beq.s match
bra.s nope
;
; found proper type ***********************************
; enter with a0.l = channel ID from parameter information
;
match move.l a5,a1 calculate address in
add.w 2(a4),a1 ... list table
cmp.l a5,a1 if local variables
ble.s nope ... prevent overlap
move.b (a1)+,d2 length of variable name
;
; a1 now points to ASCII string of variable name
;
moveq #-1,d3 timeout
moveq #IO_SSTRG,d0
trap #3 send string
;
; print line # if Procedure or Function ***************
;
; 4(a4) will point to next word in name table which will be
; the line number where the DEFine took place
;
cmp.b #4,d4
blt.s newline
lea.l line_mess,a1
move.w UT_MTEXT,a2
jsr (a2) print "Line # "
move.w 4(a4),d1 get line number
move.w UT_MINT,a2 print line number
jsr (a2)
newline moveq #LINEFEED,d1 send linefeed
moveq #-1,d3 timeout
moveq #IO_SBYTE,d0
trap #3
;
; update pointers and continue search
;
nope add #8,a4 next item in table
cmp.l a4,d5 reached end of table
bge.s search if not continue
rts end reached
;
; Message tables ******************************************
; Important: the spacing between quotes is crucial!
;
vars_mess dc.w 12
dc.b LINEFEED
dc.b 'Variables:' no spaces
dc.b LINEFEED
;
array_mess dc.w 10
dc.b LINEFEED
dc.b 'Arrays: ' 1 space after colon
dc.b LINEFEED
;
proc_mess dc.w 14
dc.b LINEFEED
dc.b 'PROCedures: ' 1 space after colon
dc.b LINEFEED
;
func_mess dc.w 12
dc.b LINEFEED
dc.b 'FuNctions:' no spaces
dc.b LINEFEED
;
line_mess dc.w 9
dc.b ' Line # ',0 insure word boundary
; 2 spaces before Line
; 1 space after #
;
par_sep ds.w 1 program variable
Listing 3 — Boot program to install vdump PROCedure
100 REMark **** boot to load vdump Procedure
110 REMark **** this procedure is called with channel
120 REMark **** i.e. <vdump #2>
125 REMark **** CTL F5 will freeze screen during dump
130 a=RESPR(332): LBYTES mdv1_vardump_proc,a
140 CALL a
Table 1 — vdump of Listing 1
Variables: a b number device$ newdevice$ count check_sum a$ c Arrays: dummy PROCedures: checking Line # 460 device Line # 300 into_machine Line # 120 to_medium Line # 630 test Line # 390 FuNctions: ready Line # 720
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.