Mastering Sideways ROM & RAM - Module 03 - One-command interpreter ------------------------------------------------------------------ In module 2 you were introduced to service calls, ROM headers and interpreters. In this module I will show how to trap a service call and create an interpreter to recognise a new * command. If you type a command, such as *SILLY, which is not recognised by the MOS, the command is first offered to the sideways ROMs and then to the currently active filing system. Load the object code generated by the program TRACE into sideways RAM and press the Break key. You will see the now familiar service call trace first used in module 2. Type a few * commands that the MOS will not recognise. Try *SILLY if you can't think of anything better. You will see the trace shown in figure 3.1. The MOS offers the command to the sideways ROMs using service call 4, Unrecognised Command. The NMI claim is used by the DFS and the Break service call is offered to all the ROMs before the "bad command" error message is printed by BASIC. >*SILLY A=04 X=0F Y=01 Unrecognised command A=0C X=0F Y=FF NMI claim A=06 X=0F Y=00 Break Bad command > Figure 3.1 Service trace for unrecognised command. -------------------------------------------------- When the unrecognised command service call is issued the ROMs are entered at the service entry point. &F2 and &F3 are used as text pointers to the command with an offset in the Y register pointing past the asterisk and any leading spaces. Try typing a few spaces after the asterisk and before the ASCII string of your command and watch the effect on the Y register in the trace. &F2 and &F3 point to the unrecognised command in the form of an ASCII string terminated by &0D. The interpreter has to use the text pointers and the Y register to compare every byte of the unrecognised command with every byte of your new command. If every pair of bytes is identical then the command is recognised and your new routine can be executed. If it is not recognised your ROM image must return control to the MOS with all the registers preserved. A simple one-command interpreter can compare the title string in the header with the unrecognised command. You must choose the title string with care. It must only use upper case alphabetic characters (because the command will be forced to upper case by ANDing every character with #&DF) and it must not conflict with any other * commands used in your computer. You cannot use titles such as "SAVE" or "INFO" for obvious reasons but you should also beware of title strings such as "DESELECT" or "DRAW" which, if they are compared with the shortened commands *DES. and *DR., will conflict with DESTROY and DRIVE. Figure 3.2 illustrates one method of making the comparison and jumping to a new routine when the unrecognised command matches the title string. It uses absolute indexed addressing with the X register and post-indexed indirect addressing with the Y register to compare every byte of the title string with every byte of the unrecognised command. The service entry point of the ROM image in figure 3.2 has a jump to the label ".service" which is the begining of the interpreter. .title EQUS "SILLY" \ title string in the header. BRK \ first byte of copyright string. . . . .service CMP #4 \ is it an unrecognised command? BEQ unrecognised \ branch if it is RTS \ return to MOS if it is not .unrecognised PHA \ push A TXA PHA \ push X TYA PHA \ push Y LDX #&FF \ initialise X .comloop INX \ increment offset on title string LDA title,X \ check if last byte of title. BEQ found \ branch if last byte of title. LDA (&F2),Y \ read next character of command. INY \ increment offset to next character CMP #ASC(".") \ is the character read a "."? BEQ found \ branch if it is a shortened command. AND #&DF \ force upper case. CMP title,X \ is it the same as the title character? BEQ comloop \ if it is compare next character PLA \ if not pull Y TAY \ restore Y PLA \ pull X TAX \ restore X PLA \ restore A RTS \ and return to MOS. .found . \ your new routine . \ goes in here . PLA \ pull Y and discard PLA \ pull X and discard PLA \ pull A and discard LDA #0 \ other ROMs ignore this request. RTS \ return to MOS Figure 3.2 An unrecognised * command interpreter. ------------------------------------------------- The trace program has shown you that, when an unrecognised * command is passed to the sideways ROMs, each ROM is entered at the service entry point with the accumulator = 4, the X register = the number of the ROM, and the Y register = the offset to the unrecognised command. The interpreter compares the accumulator with 4 to see if it is an unrecognised command. If it is control is passed to the label ".unrecognised", if it is not control is returned to the MOS with the A, X, and Y registers all unaltered. Before comparing the unrecognised command with the title string of the ROM, the A, X and Y registers are pushed on the stack so that they can be restored if the unrecognised command is not identical to the title string. After pushing the registers every byte of the title string is compared with every byte of the unrecognised command. If the comparison fails before every byte has been compared the registers are restored and control is returned to the MOS. There are two conditions when the command is recognised by the interpreter. 1. If every byte of the unrecognised command is identical to every byte of the title string the interpreter will eventually read the zero byte at the end of the title. 2. If every byte of the title string is identical to every byte of the unrecognised command until a "." terminates the unrecognised command. When the unrecognised command has been recognised by the interpreter control is passed to the label ".found" which will be the beginning of any new routine you care to use. When your new routine has finished the Y, X and A registers pushed onto the stack by the interpreter must be pulled off and discarded. Your program must pull the Y, X and A registers off the stack but there is no need to transfer A to Y and X with the first two pulls because the index registers will be ignored when you return with A=0. After the third PLA, your program must load the accumulator with zero before returning to the MOS. This will inform all other ROMs that the service request has been recognised. This simple interpreter has been used to convert the program ZAP into its sideways RAM equivalent NEWCOM. In the program ZAP the Assembly language coding from line 70 to line 230 is the equivalent of the BASIC ENVELOPE and SOUND commands. The program uses Osword 8 with a 14 byte parameter block (which corresponds with the 14 parameters in the ENVELOPE command) and Osword 7 with an 8 byte parameter block (which corresponds with the 4 parameters in the sound command). Line 270 executes the machine code generated by the Assembly language coding. When the program has been converted into sideways RAM format and loaded into sideways RAM the entire program can be replaced with the new command *ZAP. 10 REM: ZAP 20 DIM mcode 100 30 osword=&FFF1 40 FOR pass = 0 TO 2 STEP 2 50 P%=mcode 60 [ OPT pass 70 LDA #8 80 LDX #envelope MOD 256 90 LDY #envelope DIV 256 100 JSR osword 110 LDA #7 120 LDX #sound MOD 256 130 LDY #sound DIV 256 140 JSR osword 150 RTS 160 .envelope 170 OPT FNequd(&7FFC0101) 180 OPT FNequd(&8D65830A) 190 OPT FNequd(&8100FF7F) 200 OPT FNequw(&7E7E) 210 .sound 220 OPT FNequd(&00010011) 230 OPT FNequd(&00640000) 240 .lastbyte 250 ] 260 NEXT 270 CALL mcode 280 END 290 DEFFNequw(word) 300 ?P%=word 310 P%?1=word DIV 256 320 P%=P%+2 330 =pass 340 DEFFNequd(double) 350 !P%=double 360 P%=P%+4 370 =pass The program NEWCOM illustrates the conversion into sideways RAM format using the simple one-command interpreter described above. The ROM type byte in line 170 of NEWCOM indicates to the MOS that the ROM image has a service entry. The interpreter (lines 270-530) compares the unrecognised command with the title string and if they match contol is passed to the label ".found" in line 540. The code taken from the program ZAP is inserted after the label ".found" in NEWCOM but the RTS instruction in line 150 of ZAP has been replaced with PLA : PLA : PLA : LDA #0 : RTS in lines 630 to 670 of NEWCOM in order to balance the stack and load the accumulator with zero before returning control to the MOS. 10 REM: NEWCOM 20 MODE7 30 HIMEM=&3C00 40 DIM save 50 50 diff=&8000-HIMEM 60 comvec=&F2 70 gsread=&FFC5 80 osword=&FFF1 90 oscli=&FFF7 100 FOR pass = 0 TO 2 STEP 2 110 P%=HIMEM 120 [ OPT pass 130 BRK 140 BRK 150 BRK 160 JMP service+diff 170 OPT FNequb(&82) 180 OPT FNequb((copyright+diff) MOD 256) 190 BRK 200 .title 210 OPT FNequs("ZAP") 220 .copyright 230 BRK 240 OPT FNequs("(C)1987 Gordon Horsington") 250 BRK 260 .service 270 CMP #4 280 BEQ unrecognised 290 RTS 300 .unrecognised 310 PHA 320 TXA 330 PHA 340 TYA 350 PHA 360 LDX #&FF 370 .comloop 380 INX 390 LDA title+diff,X 400 BEQ found 410 LDA (comvec),Y 420 INY 430 CMP #ASC(".") 440 BEQ found 450 AND #&DF 460 CMP title+diff,X 470 BEQ comloop 480 PLA 490 TAY 500 PLA 510 TAX 520 PLA 530 RTS 540 .found 550 LDA #8 560 LDX #((envelope+diff) MOD 256) 570 LDY #((envelope+diff) DIV 256) 580 JSR osword 590 LDA #7 600 LDX #((sound+diff) MOD 256) 610 LDY #((sound+diff) DIV 256) 620 JSR osword 630 PLA 640 PLA 650 PLA 660 LDA #0 670 RTS 680 .envelope 690 OPT FNequd(&7FFC0101) 700 OPT FNequd(&8D65830A) 710 OPT FNequd(&8100FF7F) 720 OPT FNequw(&7E7E) 730 .sound 740 OPT FNequd(&00010011) 750 OPT FNequd(&00640000) 760 .lastbyte 770 ] 780 NEXT 790 INPUT'"Save filename = "filename$ 800 IF filename$="" END 810 $save="SAVE "+filename$+" "+STR$~(HIMEM)+" "+STR$~(las tbyte)+" FFFF8000 FFFF8000" 820 X%=save 830 Y%=X% DIV 256 840 *OPT1,2 850 CALL oscli 860 *OPT1,0 870 END 880 DEFFNequb(byte) 890 ?P%=byte 900 P%=P%+1 910 =pass 920 DEFFNequw(word) 930 ?P%=word 940 P%?1=word DIV 256 950 P%=P%+2 960 =pass 970 DEFFNequd(double) 980 !P%=double 990 P%=P%+4 1000 =pass 1010 DEFFNequs(string$) 1020 $P%=string$ 1030 P%=P%+LEN(string$) 1040 =pass