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 :
Count how many parameters were supplied. There must be 3 or 4.
If 4 parameters supplied, check that the first parameter has a hash in front of it.
Fetches all parameters onto the maths stack.
Convert parameters from maths stack to registers & validates them.
Set the paper, strip and ink colours for the correct channel, defaulting to #1 as appropriate, if only 3 parameters were supplied.
Clear the channel using CLS.
Returns to SuperBasic.
Abort nicely whenever it detects an error.
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'.