3. Procedures

Procedures in assembly are very much like PROCedures in SuperBasic. For example, consider the following :

1000 DEFine PROCedure PSI_CLS(chan%, P%, S%, I%)
1010   PAPER #chan%, P%
1020   STRIP #chan%, S%
1030   INK #chan%, I%
1040   CLS #chan%
1050 END DEFine PSI_CLS

This simple routine is probably at the heart of many SuperBasic programs and is called like this :

100 PSI_CLS 1, 2, 4, 0

To give channel #1 red paper, green strip and black ink. Assembler procedures are very similar and in fact we shall now dive straight in and convert the above into assembler.

Back into the QED editor with the code from the start of this article typed in. We have so far typed the code to link the new procedure and the definition block for the new procedure, now we need to write the code for the procedurte itself. Your file should look like this so far :

start   lea     define,a1       ; Pointer to the definition table
        move.w  BP_INIT,a2      ; The vector we need to use (= $110)
        jsr     (a2)            ; Call the vectored routine
        rts                     ; And return any errors back to SuperBasic

define  dc.w    1               ; 1 new procedure
        dc.w    psi_cls-*
        dc.b    7,'PSI_CLS'
        dc.w    0               ; End of procedures

        dc.w    0               ; Number of functions
        dc.w    0               ; End of functions

In the definition table there is an offset word to the start address of the new procedure. Ours is defined like this :

        dc.w    psi_cls-*

Which is a useful way to get the assembler to calculate the offset for us. The '*' is assembler short-hand for 'where I am now' or 'the current address'. Our example uses the label psi_cls so our code has to start there.

On with the procedure. In assembler you must take great care to ensure that you have enough parameters etc (see below) and that they are all the correct type. In this example, we will get using integer parameters but the first one must have a hash (#) in front of it. Of course, when using INK, PAPER etc in SuperBasic, you can default the channel number and #1 will be used instead. This means that the following statements are equivalent :

2000 PAPER #1,2
2010 PAPER 2

It would be nice if our PSI_CLS routine did a similar thing so that the following was equivalent :

2500 PSI_CLS #1, 2, 2, 0
2510 PSI_CLS 2, 2, 0

This turns out to be quite easy to do.

Here then, is a list of what our procedure must do :

Type the following after the definition block :

err_bp      equ     -15                 ; Bad parameter error
err_no      equ     -6                  ; Channel not open
bv_chbas    equ     $30                 ; Offset to channel table
bv_chp      equ     $34                 ; Offset to channel table end
bv_rip      equ     $58                 ; Maths stack pointer

psi_cls     move.l  a5,d7               ; End of parameters
            sub.l   a3,d7               ; Minus start of parameters
            divu    #8,d7               ; How many parameters?
            cmpi.w  #3,d7               ; Are we defaulting channel id?
            beq.s   hash_ok             ; yes, skip hash check

*------------------------------------------------------------------------------
* We do not have 3 parameters so test for 4 and if not found, error exit. If we
* do have 4 then the first must have a hash in front.
*------------------------------------------------------------------------------
hash_check  cmpi.w  #4,d7               ; We better only have 4 parameters
            bne.s   error_bp            ; Oops !
            btst    #7,1(a6,a3.l)       ; Is there a hash before parameter 1?
            beq.s   error_bp            ; No, we reject it then

hash_ok     move.w  ca_gtint,a2         ; We want word integers
            jsr     (a2)                ; Fetch them all
            tst.l   d0                  ; Did it work?
            beq.s   got_ok              ; Yes it did
            rts                         ; Bale out with error code otherwise

*------------------------------------------------------------------------------
* We expected to get 3 or 4 parameters and should have, but now that we have
* got them, chack to make sure we have received that which we expected to.
*------------------------------------------------------------------------------
got_ok      cmpi.w  #4,d3               ; Were there 4 of them?
            beq.s   got_4               ; Yes

            cmpi.w  #3,d3               ; Maybe default channel in use
            beq.s   got_3               ; So that is ok too

error_bp    moveq   #err_bp,d0          ; Bad Parameter error code
error_exit  rts                         ; Bale out with error

*------------------------------------------------------------------------------
* We have 4 parameters, so fetch the channel id into D0 - this is the first
* of the parameters. We need to tidy the maths stack as well so that get_rest
* works correctly regardless of whether we have 3 or 4 parameters.
*------------------------------------------------------------------------------
got_4       move.w  0(a6,a1.l),d0       ; Get channel id
            bmi.s   error_bp            ; We don't like negative channels
            adda.l  #2,a1               ; Tidy stack pointer
            bra.s   get_rest            ; Skip the default channel id bit


*------------------------------------------------------------------------------
* At this point we default the channel being used to #1. By moving one to D0
* and processing as normal, we can do this without much effort.
*------------------------------------------------------------------------------
got_3       moveq   #1,d0               ; Default channel is #1

*------------------------------------------------------------------------------
* Here convert the SuperBasic channel number in D0 into an internal id in A0
* and bale out if it fails, or if the channel is not open or has been closed
* - there is a difference. A closed channel has a negative id while a channel
* not yet opened is not in the table.
*------------------------------------------------------------------------------
get_rest    bsr     channel_id          ; Convert DO to QDOS id in A0.L
            bne.s   error_exit          ; Bale out if errors

At this point we have (A6,A1) pointing to the paper parameter on the stack and A0.L holding the channel id for the requested channel (or the default of #1). Now we can set the paper colour (which does not set the strip like SuperBasic does !)

Looking at the QDOS documentation for SD_SETPA and the others, we see that A1 is 'undefined' on return from the routine. This is bad so we need to preserve it across calls or we can fetch all the parameters first. Registers D4 to D7 are not mentioned in the documentation so they are preserved/not used by the routines so we shall fetch the parameters into these registers first of all and this way we can also validate them for errors.

*------------------------------------------------------------------------------
* Because we tidied the stack pointer in A1 when we fetched the channel id, the
* following code expects to see the paper colour at 0(A6,A1) and this is the
* same as if we never were supplied with a channel id in the first place -
* cunning stuff eh?
*
* Fetch the remaining 3 parameters into registers that will not be trashed by
* the QDOS routines that set the paper, stip and ink. We reject any parameter
* which is negative as we don't deal with negative colours and just in case, we
* also mask out the high work of the parameter to ensure it is in range 0 to 255
*
* NOTE : we could do away with the negative check and just mask. This would in
* effect convert from a negative to a positive number - but this is the real
* world (?) and we have to perform parameter validation.
*------------------------------------------------------------------------------
            move.w  0(a6,a1.l),d4       ; Paper in D4
            bmi.s   error_bp            ; Negative is bad news
            andi.w  #$00ff,d4           ; Force range 0 - 255

            move.w  2(a6,a1.l),d5       ; Strip in D5
            bmi.s   error_bp            ; Negative is bad news
            andi.w  #$00ff,d5           ; Force range 0 - 255

            move.w  4(a6,a1.l),d6       ; Ink in D6
            bmi.s   error_bp            ; Negative is bad news
            andi.w  #$00ff,d6           ; Force range 0 - 255

            adda.l  #6,a1               ; Tidy the stack

            moveq   #sd_setpa,d0        ; Paper trap code
            move.w  d4,d1               ; Paper colour
            moveq   #-1,d3              ; Infinite timeout
*                                       ; Channel id is still in A0
            trap    #3                  ; Set the paper
            tst.l   d0                  ; OK?
            bne.s   error_exit          ; No bale out

*------------------------------------------------------------------------------
* Now the paper has been set, and the documentation says that A0 is preserved
* along with D3, we can set the strip colour now.
*------------------------------------------------------------------------------
            moveq   #sd_setst,d0        ; Strip trap code
            move.w  d5,d1               ; Strip colour
            trap    #3                  ; Set the strip
            tst.l   d0                  ; OK?
            bne.s   error_exit          ; No bale out

*------------------------------------------------------------------------------
* Now the strip has been set, and the documentation says that A0 is preserved
* along with D3, we can set the ink colour now.
*------------------------------------------------------------------------------
            moveq   #sd_setin,d0        ; Ink trap code
            move.w  d6,d1               ; Ink colour
            trap    #3                  ; Set the Ink
            tst.l   d0                  ; Ok?
            bne.s   error_exit          ; No bale out

*------------------------------------------------------------------------------
* And finally, we can CLS the screen. You have seen this before in QLTdis.
*------------------------------------------------------------------------------
            moveq   #sd_clear,d0        ; CLS whole screen
            trap    #3                  ; Do it
            bra.s   error_exit          ; All done


*------------------------------------------------------------------------------
* This routine takes a SuperBasic channel number in D0 and converts it
* into a QDOS internal channel id in A0. If the channel is closed or not
* yet opened, the routine returns D0 = ERR_NO and A0 is invalid.
* D0 will be zero if all is ok.
*------------------------------------------------------------------------------
channel_id  mulu    #$28,d0             ; Offset into channel table
            add.l   bv_chbas(a6),d0     ; Add table start address
            cmp.l   bv_chp(a6),d0       ; Valid?
            bge.s   ch_bad              ; No, channel # off end of table
            move.l  0(a6,d0.l),d0       ; Channel id
            bmi.s   ch_bad              ; Channel closed
            move.l  d0,a0               ; We need id in A0
            moveq   #0,d0               ; No errors
            rts                         ; Finished

ch_bad      moveq   #err_no,d0          ; Channel not open (-6)
            rts                         ; Bale out

Save the file and assemble it using GWASL. Once all errors have been sorted out, either LRESPR it or ALCHP/LBYTES/CALL in the normal manner. If you have a JM and below, type NEW then try this :

PSI_CLS #1, 2, 4, 0  (or PSI_CLS 2, 4, 0)

And see what happens when you

PRINT 'Hello world' (or PRINT #1, 'Hello world')

If you have a JS or above, then just try it without the NEW.

You should see the words 'Hello world' written in black, on a green strip on red paper - assuming your display can handle the colour mixture !

In the code, you will notice that whenever I detect an error, I simply return to SuperBasic with the error code in D0. This doesn't look very friendly does it? Actually, QDOS is very friendly when it comes to procedures because in the event of an error, QDOS will do all the tidying up that we need to do so we don't have to worry about it. This is discussed below in 'FUNCTIONS' and in 'MATHS STACK'.