Mastering Sideways ROM & RAM - Module 05 - Reading arguments ------------------------------------------------------------ In this module I will extend the interpreter introduced in Module 3 to include reading arguments which follow the * command. There are two MOS subroutines used to read arguments. They are Gsinit at &FFC2 and Gsread at &FFC5. Gsinit should be used to initialise an argument which is to be read using Gsread. Gsinit uses the text pointers at &F2 and &F3 with an offset in the Y register to identify the argument address. The argument can be enclosed by quotation marks but it does not have to be. Gsinit is used to strip off any leading spaces and to set up an information byte in zero page which indicates the termination character of the argument. Before calling Gsinit you should clear the carry flag if you want the second quotation mark, the first space within the argument, or a carriage return as the terminating character. You should set the carry flag before calling Gsinit if you do not want a space to be recognised as a terminating character. If in doubt, set the carry flag before calling Gsinit. After calling Gsinit the Y register contains an offset to the first character of the argument and the accumulator contains the first character of the argument. The zero flag is set if the argument is a null string. For example, imagine you have typed *CURSOR ON and the interpreter has recognised the command CURSOR. Before calling Gsinit, the text pointers at &F2 and &F3 will point to the command with the offset in Y pointing to the second R of CURSOR. After calling Gsinit the offset in Y will point to the O of ON, the zero flag will be clear, and the terminating character will be the carriage return after the N of ON. If ON is enclosed by quotation marks the offset in Y will still point to the O of ON and the zero flag will be clear, but the terminating character will be the second quotation mark. If an argument is not typed or if *CURSOR "" is typed the zero flag will be set on return from Gsinit. Figure 5.1 shows the offset in the Y register pointing to the end of the command before calling Gsinit and pointing to the beginning of the argument after calling Gsinit. +- Y before Gsinit -+ ! ! v v >*CURSOR ON >*CURSOR "ON" ^ ^ ! ! +-- Y after Gsinit --+ Figure 5.1 The offset in Y before and after calling Gsinit. ----------------------------------------------------------- Gsread is used to read each byte of the argument. This routine should only be used following a Gsinit call or a previous Gsread. Following Gsinit the text pointers and Y register will point to the first byte of the argument. After calling Gsread the accumulator contains the character read from the argument, Y is incremented to point to the next character, X is preserved and the carry flag is set if the terminating character has been read. The effect of calling Gsread is similar to, but not the same as: LDA (&F2),Y INY The difference exists because Gsread tests the content of A to see if the terminating character, and hence the end of the argument, has been read. The terminating character can be either a second quotation mark, a space or a carriage return as defined by Gsinit. Using Gsinit to initialise an argument and Gsread to read it is more versatile than simply reading the argument using post-indexed indirect addressing with the text pointers. The difference may seem insignificant with the simple example used above but it is useful if the argument is a number when the place value of a particular character depends on its displacement from the terminating character. Reading numerical arguments will be covered in module 11. Because the effect of using Gsread is very similar to reading a string using post-indexed indirect addressing with the text pointers you may be tempted to use Gsread in the interpreter to read the unrecognised command. If you do you will find that most of the time it will work but, sooner or later, you will start getting mysterious "bad string" error messages. Don't be tempted into wasting your time using Gsread in the interpreter. Stick with the method demonstrated in Module 3. Figure 5.2 shows how the interpreter used in the last two modules can be modified to read an argument. .found SEC \ terminate with Return or " JSR &FFC2 \ initialise argument with Gsinit BEQ exit \ branch if null argument JSR &FFC5 \ read first character with Gsread BCS exit \ branch if end of argument AND #&DF \ force upper case . . . .exit PLA \ discard Y PLA \ discard X PLA \ discard A LDA #0 \ service call recognised RTS \ return to MOS Figure 5.2 Using Gsinit and Gsread to read an argument. ------------------------------------------------------- The carry flag is set before calling Gsinit. The terminating character of the argument will be a carriage return or a second quotation mark. After setting the carry flag Gsinit is called to initialise the argument. If the argument is null Gsinit returns with the zero flag set. This can be tested and branch to exit if there is nothing to read. If the zero flag is clear there is an argument to be read by calling Gsread for each character. Gsread will return with the carry flag set when the terminating character is read and an appropriate branch can be made after testing the carry flag. This method of reading an argument has been used in the progran CURSOR. When the interpreter (lines 280-540) recognises the command *CURSOR it passes control to the label ".found" (line 550). The argument is initialised (lines 560-570) and the argument is tested (line 580). If a null argument is detected the program returns control to the MOS with A=0 (lines 860-910). If an argument is found the program expects to read either ON or OFF. To test for ON or OFF the first character can be skipped since it should be an "O" (line 590). The next character is read using Gsread (line 600) and tested for a terminating character (line 610) and either "F" (line 630) or "N" (line 650). There is no need to read and test for the second F of OFF. If the second character of the argument is "F" the cursor is switched off (lines 690-850), if it is "N" the cursor is switched on (lines 670-850). When you load the object code generated by CURSOR into SWR to see how it works it is possible to deceive yourself into believing that there is something wrong with it. If you type *CURSOR OFF and press Return the cursor will go off as expected. Type *CURSOR ON and it will come back on. Everything seems OK. Type *CURSOR OFF and Return and then type *CURSOR and Return. The cursor stays off because you have not typed an argument. Everything is still OK. Now use the cursor keys to copy the *CURSOR command without an argument and press Return. The cursor comes back on when it should have stayed off. This will happen because you will re-enable the cursor with the cursor keys. There is nothing wrong with the argument reading technique demonstrated in the program. 10 REM: CURSOR 20 MODE7 30 HIMEM=&3C00 40 DIM save 50 50 diff=&8000-HIMEM 60 comvec=&F2 70 gsinit=&FFC2 80 gsread=&FFC5 90 osasci=&FFE3 100 oscli=&FFF7 110 FOR pass = 0 TO 2 STEP 2 120 P%=HIMEM 130 [ OPT pass 140 BRK 150 BRK 160 BRK 170 JMP service+diff 180 OPT FNequb(&82) 190 OPT FNequb((copyright+diff) MOD 256) 200 BRK 210 .title 220 OPT FNequs("CURSOR") 230 .copyright 240 BRK 250 OPT FNequs("(C)1987 Gordon Horsington") 260 BRK 270 .service 280 CMP #4 290 BEQ unrecognised 300 RTS 310 .unrecognised 320 PHA 330 TXA 340 PHA 350 TYA 360 PHA 370 LDX #&FF 380 .comloop 390 INX 400 LDA title+diff,X 410 BEQ found 420 LDA (comvec),Y 430 INY 440 CMP #ASC(".") 450 BEQ found 460 AND #&DF 470 CMP title+diff,X 480 BEQ comloop 490 PLA 500 TAY 510 PLA 520 TAX 530 PLA 540 RTS 550 .found 560 SEC \ Terminate with Return or " 570 JSR gsinit 580 BEQ exit \ Branch if null argument 590 INY \ First character must be "O" 600 JSR gsread \ Second character "F" or "N" 610 BCS exit \ Branch if end of argument 620 AND #&DF \ Upper case 630 CMP #ASC("F") 640 BEQ cursoroff 650 CMP #ASC("N") 660 BNE exit 670 SEC 680 BCS cursor 690 .cursoroff 700 CLC 710 .cursor 720 PHP 730 LDA #23 740 JSR osasci 750 LDA #1 760 JSR osasci 770 PLA 780 AND #1 790 JSR osasci 800 LDA #0 810 LDX #7 820 .cursorloop 830 JSR osasci 840 DEX 850 BNE cursorloop 860 .exit 870 PLA 880 PLA 890 PLA 900 LDA #0 910 RTS 920 .lastbyte 930 ] 940 NEXT 950 INPUT'"Save filename = "filename$ 960 IF filename$="" END 970 $save="SAVE "+filename$+" "+STR$~(HIMEM)+" "+STR$~(las tbyte)+" FFFF8000 FFFF8000" 980 X%=save 990 Y%=X% DIV 256 1000 *OPT1,2 1010 CALL oscli 1020 *OPT1,0 1030 END 1040 DEFFNequb(byte) 1050 ?P%=byte 1060 P%=P%+1 1070 =pass 1080 DEFFNequw(word) 1090 ?P%=word 1100 P%?1=word DIV 256 1110 P%=P%+2 1120 =pass 1130 DEFFNequd(double) 1140 !P%=double 1150 P%=P%+4 1160 =pass 1170 DEFFNequs(string$) 1180 $P%=string$ 1190 P%=P%+LEN(string$) 1200 =pass