.........1.........2.........3.........4.........5.........6.........7.........8 Mastering Machine Code on Your Spectrum part 8 of 8 - from ZX Computing Feb/Mar'84 Sadly we've reached the last part of Toni Baker's superb series that takes the mystery out of Machine Code. But don't despair, she will be back and has promised us something special for the next issue ... This is to be the last in the series of articles bearing this title. This doesn't mean that I'm not writing for the magazine any more - just that the next time around I'll be starting on something different. However, SINCE this is the last MMC bit, I thought I'd go out with a bang. I'd like to list for you what is possible the most sophisticated machine code program ever printed in any computing magazine. This is a WORD PROCESSOR program: I call it "WordSheep" in order to stop things from getting too serious. Let me describe to you what it does first of all, starting with the limitations. Firstly, you may only work with one screenful of text at a time. This means that if you want to type out a long letter you must treat it in separate parts, although note that this won't actually matter, because using the ZX printer there will be no "join" between the screenfuls. You can't do clever things like copying whole paragraphs all over the place or shuffling blocks of text around. Here's what you CAN do: The LETTER keys produce the letters of the alphabet - either in upper or lower case as required. You should not type ENTER between lines because the program will sort all that out for itself, straightening the right-hand margin in the process. The SPACE key works as you'd expect, although the actual number of spaces between words is determined by the program and not by you, so that if you type five spaces between two words this will be compressed to one (or more, as required for the right-hand margin to be straight). The NUMBER keys do exactly what you'd expect them to. There are fourteen CONTROL FUNCTIONS available to you, and these are as follows: EDIT (caps shift 1) Bring down one line for editing CAPS LOCK (caps shift 2) Change from L mode to C mode & vice versa CLEAR LINE (caps shift 3) Erase one line of text CLEAR SCREEN (caps shift 4) Blank the whole screen CURSOR LEFT (caps shift 5) Move cursor left CURSOR DOWN (caps shift 6) Move cursor down CURSOR UP (caps shift 7) Move cursor up CURSOR RIGHT (caps shift 8) Move cursor right COPY (caps shift 9) Copy the screen onto the ZX printer DELETE (caps shift 0) Delete one character at the cursor position ENTER (enter) End of paragraph REFORM (symbol shift Q) Reconstruct paragraph after alterations have been made EXIT (symbol shift W) Return to BASIC YOURS (both shifts) User Defined Control Function - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Symbol shift in this program works slightly different to what you're used to. The differences are as follows: Symbol Shift Q and Symbol Shift W are as above. Symbol Shift E produces the copyright symbol '(c)' (character 7F). Symbol Shift I produces something called a SOLID SPACE. This looks like a space to me and you, but isn't treated like one by the computer. It is in fact the graphics character whose code is 80. It may be used when specific spacing is required. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - All other keys with Symbol Shift will produce the ASCII character which is printed in read either on or below that key. This means that you do not need to enter 'E' mode in order to obtain curly brackets - you simply press Symbol Shift F and G. (Note that 'E' mode is not used at all in this program, and that no keywords or tokens may be obtained.) [There were about 20 printing errors in the magazine listing for this. JG.] OK - here's the program. In order to minimise errors I shall for a change include the absolute addresses which I have used. You may change these of course - for instance subtracting 8000 from all my addresses will allow the program to run on a 16K Spectrum. The only restriction on changing my addresses is that the second and third tables at the start of the program must use the same high-part address all the way through - in my case 'EA'. Before you start typing in the listing note that the program uses 'GRAPHIC A' which is defined thus: 00 GRAPHIC_A DEFB 00000000b 00 DEFB 00000000b FF DEFB 11111111b 00 DEFB 00000000b 00 DEFB 00000000b FF DEFB 11111111b 00 DEFB 00000000b 00 DEFB 00000000b - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - And now the program. First come three tables which will be constructed by the program later on: ORG E800 U_TABLE DEFS 0280 Occupies addresses E800 to EA7F L_TABLE DEFS 60 Occupies addresses EA80 to EADF S_TABLE DEFS 20 Occupies addresses EAE0 to EAFF - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The first subroutine is used to delete all unwanted spaces in the line being edited. This line resides in L_TABLE (Lower Screen Table). ORG EB00 21E0EA COMPRESS LD HL,#EAE0 HL points one byte beyond L_TABLE 3E20 LD A,"space" 065F LD B,#5F B:=number of character pos to check 2B C_1 DEC HL HL points to next byte in L_TABLE BE CP (HL) Is there a space at this location? 2003 JR NZ,C_2 Jump if a non-space character found 10FA DJNZ C_1 Repeat for whole file (Note it is not necessary to check very 1st character) C9 RET Exit if the file is full of spaces (except possibly for the first byte) 2E80 C_2 LD L,#80 HL points to first byte of L_TABLE 0E01 LD C,#01 C is 'space to be deleted' flag BE C_3 CP (HL) Is there a space at this location? 2015 JR NZ,C_5 Jump if not 0D DEC C Test 'space to be deleted' flag 200E JR NZ,C_4 Jump if space is not to be deleted C5 PUSH BC 04 INC B B:=number of bytes to move E5 PUSH HL 54 LD D,H 5D LD E,L DE:=address of space to be deleted 23 INC HL HL:=address of next byte 48 LD C,B 0600 LD B,#00 BC:=number of bytes to move EDB0 LDIR Delete space E1 POP HL 2B DEC HL HL points to first undeleted space C1 POP BC B:=number of bytes left to check 0E01 C_4 LD C,#01 Set 'space to be deleted' flag 1802 JR C_6 0E00 C_5 LD C,#00 Reset flag (space not to be deleted) 23 C_6 INC HL Point to next character along 10E3 DJNZ C_3 Repeat for as many bytes as necessary C9 RET End of subroutine - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The table S_TABLE (Spaces Table) stores a sequence of bytes, each of which is the low part of the address of a "space" character somewhere in L_TABLE. The job of this next sub- routine is to insert 'C' spaces into L_TABLE at the address whose low part is stored at address HL (ie. insert 'C' spaces at address H*100h+(HL)). ORG EB30 F5 INS_SPACE PUSH AF C5 PUSH BC D5 PUSH DE E5 PUSH HL 6E LD L,(HL) HL:=address of existing space E5 PUSH HL Stack this address 2EDF LD L,#DF HL points to last byte of L_TABLE 0600 LD B,#00 BC:=number of spaces to insert A7 AND A ED42 SBC HL,BC HL points to last byte which will remain after insertion 54 LD D,H 5D LD E,L DE:=address of last byte to remain E1 POP HL HL:=address of existing space 2B DEC HL EB EX DE,HL C5 PUSH BC E5 PUSH HL A7 AND A ED52 SBC HL,DE HL:=number of bytes to move 44 LD B,H 4D LD C,L BC:=number of bytes to move E1 POP HL HL:=address of last byte to remain 11DFEA LD DE,#EADF DE points to last byte in L_TABLE EDB8 LDDR Move required characters EB EX DE,HL HL points to last new position C1 POP BC BC:=number of bytes inserted 41 LD B,C B:=number of bytes inserted 3620 IS_LOOP LD (HL),"space" Overwrite next byte 2B DEC HL Point to next byte to overwrite 10FB DJNZ IS_LOOP Repeat for each of the new positions E1 POP HL HL points into S_TABLE E5 PUSH HL 23 IS_SPACES INC HL Point to next address low-part 7E LD A,(HL) A:=former low-part of address of space FEA0 CP #A0 Exit if not within the 1st 20h bytes 3004 JR NC,IS_EXIT 81 ADD A,C 77 LD (HL),A Update address pointer 18F6 JR IS_SPACES Repeat for all addresses E1 IS_EXIT POP HL D1 POP DE C1 POP BC Note that this subroutine leaves all registers unchanged F1 POP AF C9 RET End of subroutine - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - A rather intricate subroutine now. The purpose of this sub- routine is to insert spaces into the line being edited so that a single space occurs at the thirty-third position. Thus a complete word will end at the thirty-second position, ensuring that right-hand margins remain straight. ORG EB68 016000 ADJUST LD BC,#0060 BC:=number of bytes in L_TABLE 11E0EA LD DE,#EAE0 DE points to start of S_TABLE 2180EA LD HL,#EA80 HL points to start of L_TABLE 3E20 A_SEARCH LD A,"space" EDB1 CPIR Search for next space 2007 JR NZ,A_ST_DONE Jump if no spaces left to find 7D LD A,L 3D DEC A A:=low part of address of space 12 LD (DE),A Store in S_TABLE 1C INC E DE points to next byte in table 20F4 JR NZ,A_SEARCH If room in table repeat for next space 1D DEC E DE:=EAFF (last byte of S_TABLE) EB A_ST_DONE EX DE,HL HL points to last used byte in S_TABLE 36FF LD (HL),#FF Store end of table marker 2EE0 LD L,#E0 HL points to first byte of S_TABLE 7E LD A,(HL) A:=low part of address of 1st space FE9F CP #9F D0 RET NC Return if there are no spaces within the first 31 bytes, since adjustment would be impossible 47 A_SP_END LD B,A B:=low part of address of last space 23 INC HL Point to next element of table 7E LD A,(HL) A:=low part of address of next space FEFF CP #FF Check against end of file marker 280D JR Z,A_CH_2 Jump if end of file reached 4F LD C,A Temporarily store in C 90 SUB B A:=distance between last two spaces 3D DEC A Set zero flag if there are two spaces in a row. (This will only be the case if the end of text has been reached.) 79 LD A,C Restore A 2805 JR Z,A_CH_1 Jump if end of text reached FEA0 CP #A0 Is there a space at the 33rd position? C8 RET Z Return if so, since no adjustment is needed 18EE JR A_SP_END Otherwise check next space 36FF A_CH_1 LD (HL),#FF Mark as end of file 2EE1 A_CH_2 LD L,#E1 HL points to 2nd element of S_TABLE 3EA0 LD A,#A0 BE CP (HL) C8 RET Z Return if there is only one space in the first 32 bytes, since adjustment would be impossible D8 RET C 1E00 LD E,#00 4E A_NUMBER LD C,(HL) C:=low part of address of last space 1C INC E E counts number of spaces 23 INC HL Point to next space BE CP (HL) 30FA JR NC,A_NUMBER Repeat for all spaces within first 20h bytes 91 SUB C A:=total number of spaces to insert 0EFF LD C,#FF 0C A_MOD INC C C calculates INT (number of spaces required / number of existing spaces in range) 93 SUB E A records remainder of this division 30FC JR NC,A_MOD Note that A is adjusted once more than needed 83 ADD A,E Restore A to true remainder 57 LD D,A D:=remainder of division 0C INC C 0D DEC C 2809 JR Z,A_REMAIN Jump if division equals zero 43 LD B,E B:=number of existing spaces in range 2EE0 LD L,#E0 HL points to 1st element of S_TABLE CD30EB A_SL_1 CALL INS_SPACE Insert 'C' spaces 23 INC HL Point to next element of S_TABLE 10FA DJNZ A_SL_1 'E*C' spaces have now been inserted evenly 14 A_REMAIN INC D 15 A_SL_2 DEC D C8 RET Z Task complete - exit subroutine D5 PUSH DE 2A765C LD HL,(SEED) HL:=random number seed 54 LD D,H 5D LD E,L 29 ADD HL,HL Multiply by 2 ... 29 ADD HL,HL 4 ... 19 ADD HL,DE 5 ... 29 ADD HL,HL A ... 29 ADD HL,HL 14 ... 29 ADD HL,HL 28 ... 19 ADD HL,DE 29 ... 22765C LD (SEED),HL Store new random number seed D1 POP DE Restore D and E 7C LD A,H A:=random number 93 A_RANDOM SUB E 30FD JR NC,A_RANDOM 83 ADD A,E A:=random number between 0 and 'E-1' C6E0 ADD A,#E0 6F LD L,A 26EA LD H,#EA HL points to random element of S_TABLE 0E01 LD C,#01 C:=number of spaces to insert CD30EB CALL INS_SPACE Insert space 18DC JR A_SL_2 Correct number of spaces have now been inserted, distributed more or less evenly - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - By contrast, the next few subroutines are all really easy to follow. The job of this one is to copy text from L_TABLE to the lower part of the screen. Note that the system variable (DF_SZ) is assumed to be 04, not 02. ORG EBE5 21A050 TRANSFER_L LD HL,#50A0 HL:=print position on screen 22865C LD (DFCCL),HL Store print position 212117 LD HL,#1721 HL:=screen coordinates 228A5C LD (SPOSNL),HL Store coordinates FD360201 LD (TVFLAG),#01 Direct PRINT to lower part of screen 2180EA LD HL,#EA80 HL points to start of L_TABLE 0640 LD B,#40 B:=number of bytes to print 7E TL_PRINT LD A,(HL) A:=next character to print 23 INC HL Point to next character to print D7 RST #10 Print character 10FB DJNZ TL_PRINT Repeat for 40h characters 3AB15C LD A,(L_CURSOR) A:=low part of address of cursor in L_TABLE C620 ADD A,#20 A:=low part of address of cursor in attributes file 6F LD L,A 265A LD H,#5A HL:=address of cursor in attributes file 7E LD A,(HL) A:=attribute at that address EE3F XOR #3F Complement the colours 77 LD (HL),A Store new attribute to indicate cursor C9 RET - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Notice that the subroutine above makes use of a program variable called L_CURSOR which is one byte long and lives at 5CB1. It stores the low part of the address of the cursor within the edit-line (in L_TABLE). There is another variable used in the program and this is called U_CURSOR (Upper Cursor) which is two bytes long, and stores the address of the second cursor - this time within U_TABLE; it is always a the left- hand edge of the screen. Its address is 5CAF, and it is made use of by this next subroutine, which is called TRANSFER_U. The purpose of this subroutine is very similar to the last one - it copies text from U_TABLE to the upper part of the screen. ORG EC0C 210040 TRANSFER_U LD HL,#4000 HL:=print position on screen 22845C LD (DF_CC),HL Store print position 212118 LD HL,#1821 HL:=on-screen coordinates 22885C LD (S_POSN),HL Store coordinates FD360200 LD (TVFLAG),#00 Direct PRINT to upper part of screen 2100E8 LD HL,#E800 HL points to first bytes of U_TABLE 010380 LD BC,#8003 B:=80 and C:=03 7E TU_PRINT LD A,(HL) A:=next character to print 23 INC HL Point to next character to print D7 RST #10 Print character 10FB DJNZ TU_PRINT Repeat for either 80 or 100 characters. B:=00 (effectively 100) 0D DEC C 20F8 JR NZ,TU_PRINT Total 0280 bytes in all 2AAF5C LD HL,(U_CURSOR) HL points to cursor position in U_TABLE 7C LD A,H A:=high part D690 SUB #90 67 LD H,A HL points to cursor position in attributes file 3A8D5C LD A,(ATTR_P) A:=attribute normally used for screen EE3F XOR #3F Complement the colours WIPE_LINE $ See below - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This subroutine leads straight into a routine called WIPE_LINE which will be used quite a lot by other parts of the program. It pokes the byte held by the A register into 20h consecutive locations. In the above case it is used to complement the colours of one row of the upper part of the screen at the cursor position. It may also be called from the label WIPE, in which case the number of locations to be poked is BC+1. ORG EC36 011F00 WIPE_LINE LD BC,#001F BC:=number of locations to poke, less one 54 WIPE LD D,H 5D LD E,L DE:=address of 1st location 13 INC DE DE:=address of 2nd location 77 LD (HL),A Poke first location EDB0 LDIR Poke remaining locations C9 RET - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The next subroutine is the CLEAR_SCREEN subroutine, which is accessed upon running by Caps Shift 4. You should be able to follow it with no further explanation from me. ORG EC40 2100E8 CLEAR_SCRN LD HL,#E800 HL points to first byte of U_TABLE 22AF5C LD (U_CURSOR),HL Reset upper cursor to top line 3E80 LD A,#80 A:=low part of address of first byte of L_TABLE 32B15C LD (L_CURSOR),A Reset lower cursor to start of line 01DF02 LD BC,#02DF BC:=number of bytes in U_TABLE and L_TABLE combined, less one 3E20 LD A,"space" CD39EC CALL WIPE Fill both tables with spaces CD0CEC CALL TRANSFER_U Blank upper part of screen C3E5EB JP TRANSFER_L Blank lower part of screen - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Now things start getting really easy. This is the CURSOR LEFT routine. ORG EC59 3AB15C LEFT LD A,(L_CURSOR) A:=address of cursor (low part) FE80 CP #80 Is cursor already at left of line? C8 RET Z Return if so FD3577 DEC (L_CURSOR) Otherwise move cursor left C9 RET and return - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - And with amazing similarity, CURSOR RIGHT. ORG EC63 3AB15C RIGHT LD A,(L_CURSOR) A:=address of cursor (low part) FEBF CP #BF Is cursor already at right of line? C8 RET Z Return if so FD3477 INC (L_CURSOR) Otherwise move cursor right C9 RET and return - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This is the CURSOR UP routine. ORG EC6D 2AAF5C UP LD HL,(U_CURSOR) HL:=address of cursor 1100E8 LD DE,#E800 DE:=highest position allowable A7 AND A ED52 SBC HL,DE Is cursor already at top? C8 RET Z Return if so 11E0FF LD DE,#FFE0 DE:=vertical displacement up one line 180D JR DOWN_UP Move cursor up - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - And, as you'd expect, the CURSOR DOWN routine. ORG EC7C 2AAF5C DOWN LD HL,(U_CURSOR) HL:=address of cursor 1160EA LD DE,#EA60 DE:=lowest position allowable A7 AND A ED52 SBC HL,DE Is cursor already at bottom? C8 RET Z Return if so 112000 LD DE,#0020 DE:=vertical displacement down one line 2AAF5C DOWN_UP LD HL,(U_CURSOR) HL:=address of cursor 19 ADD HL,DE Compute new position 22AF5C LD (U_CURSOR),HL Store new position C30CEC JP TRANSFER_U Print screen showing cursor in new position - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Now we come to the EDIT function. This too is quite straight- forward. ORG EC93 2AAF5C EDIT LD HL,(U_CURSOR) HL:=address of upper cursor 1180EA LD DE,#EA80 DE:=address of 1st byte in L_TABLE FD7377 LD (L_CURSOR),E Move lower cursor to left of line 012000 LD BC,#0020 BC:=number of bytes in one line 79 LD A,C A:="space" EDB0 LDIR Copy line into L_TABLE 0E3F LD C,#3F BC:=number of bytes to erase, less one EB EX DE,HL HL:=address of first byte to erase 1892 JR WIPE Erase remainder of L_TABLE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Now we come to the CAPS LOCK function. This relies upon the fact that the L mode / C mode is stored by the ROM as bit three of the system variable FLAGS2. This is reset for L mode, or set for C mode. ORG ECA7 3A6A5C CAPS_LOCK LD A,(FLAGS_2) Fetch FLAGS_2 EE08 XOR #08 Complement bit 3 326A5C LD (FLAGS_2),A Store amended variable C9 RET - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Easier and easier, eh? The next subroutine is the COPY function, which is used by the program to copy the top nine- teen lines of the screen onto the ZX printer. ORG ECB0 F3 COPY_13 DI This is because COPY won't work if the interrupts are enabled 0698 LD B,#98 B:=number of rows to copy (Note: eight rows equals one line) C3AF0E JP COPY_B Jump into ROM COPY routine (Note: this routine automatically re-enables the interrupts) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Now comes the CLEAR_LINE routine, the purpose of which is to erase (ie. overwrite with space) one line of text from the upper part of the screen at the cursor position. ORG ECB6 2AAF5C CLEAR_LINE LD HL,(U_CURSOR) HL points to upper cursor position 3E20 LD A,"space" CD36EC CALL WIPE_LINE Erase line as required C30CEC JP TRANSFER_U Re-print upper screen with line erased - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - It is at this point that things start getting a little more intricate. This is the DELETE routine. ORG ECC1 CD59EC DELETE CALL LEFT Move cursor left if possible C8 RET Z Return if cursor at left of line 6F LD L,A 26EA LD H,#EA HL:=previous address of cursor 5F LD E,A 1D DEC E 54 LD D,H DE:=new address of cursor 2F CPL D61F SUB #1F A:=number of bytes to move 4F LD C,A 0600 LD B,#00 BC:=number of bytes to move EDB0 LDIR Byte no deleted [sic. JG.] 3E20 LD A,"space" 12 LD (DE),A Erase final character in L_TABLE ORG ECD6 C9 RET [Undefined in the magazine. JG.] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Next comes an exciting part! This is the CHARACTER routine, which controls what happens when any character in the range 20 to 80 is given. The character in question starts its life (as far as this subroutine is concerned) in the A register. ORG ECD7 F5 CHARACTER PUSH AF Stack character to add 3AB15C LD A,(L_CURSOR) A:=cursor position 21E1EA LD HL,#EAE1 HL points to 2nd byte of S_TABLE 36FF LD (HL),#FF Store an end of file marker 2B DEC HL HL points to 1st byte of S_TABLE 77 LD (HL),A Store cursor position 0E01 LD C,#01 C:=number of bytes to insert CD30EB CALL INS_SPACE Insert one space at desired location FD6E77 LD L,(L_CURSOR) HL:=address of cursor F1 POP AF A:=character to add to file 77 LD (HL),A Store character at correct point CD63EC CALL RIGHT Move cursor right for next character 2806 JR Z,PROCESS Jump forward if at right of line 3ABFEA LD A,(#EABF) A:=32nd character in file FE20 CP "space" C8 RET Z If this is a space, then routine finished PROCESS $ Otherwise ... - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Are you ready? This is the main processing routine. It may also be called from the labels PROCESS_1 and PROCESS_2. Please pay careful attention to what happens here. ORG ECF7 CD00EB PROCESS CALL COMPRESS Remove all unwanted spaces CD68EB PROCESS_1 CALL ADJUST Line up right-hand margin 2180EA PROCESS_2 LD HL,#EA80 HL points to 1st byte of L_TABLE ED5BAF5C LD DE,(U_CURSOR) DE:=address of upper cursor 012000 LD BC,#0020 BC:=number of characters in one line EDB0 LDIR Transfer one line to upper screen E5 PUSH HL Stack the constant EAA0 CD7CEC CALL DOWN Move upper cursor down if possible E1 POP HL HL points to 1st byte of 2nd line 1180EA LD DE,#EA80 DE points to 1st byte of 1st line 014000 LD BC,#0040 BC:=number of bytes to move EDB0 LDIR Delete top line from L_TABLE 3E20 LD A,"space" EB EX DE,HL HL points to 3rd line of L_TABLE CD36EC CALL WIPE_LINE Erase last line 21C0EA LD HL,#EAC0 HL points to start of 3rd line of L_TABLE 0640 LD B,#40 B:=number of bytes in 1st two lines 2B PR_LOOP DEC HL Point to next byte along BE CP (HL) Is it a space? 2003 JR NZ,PR_CURSOR Jump forward if not 10FA DJNZ PR_LOOP Try again for next byte 2B DEC HL HL:=EA7F 23 PR_CURSOR INC HL HL:=new address of cursor 7D LD A,L A:=low part of this address 32B15C LD (L_CURSOR),A Store new cursor position C9 RET - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Next we have the NEW PARAGRAPH routine - this is what happens when you hit the ENTER key. See if you can follow it. ORG ED2E CD00EB ENTER CALL COMPRESS Delete unwanted spaces 21C0EA LD HL,#EAC0 Point HL beyond all text in L_TABLE 3E20 LD A,"space" 2B E_CHECK DEC HL Point to next character BE CP (HL) Is it a space? 28FC JR Z,E_CHECK Loop back if so 7D LD A,L A:=low part of address of last non-space character in file FEA0 CP #A0 Is it on the first line? D4FAEC CALL NC,PROCESS_1 If not then process one line from lower part to upper part of screen CD00EB CALL COMPRESS Now delete any excess spaces generated by the above instruction CDFDEC CALL PROCESS_2 Copy final part of L_TABLE to screen, but without aligning right-hand margin CDB6EC CALL CLEAR_LINE Make one blank line below paragraph 2AAF5C LD HL,(U_CURSOR) HL:=address of upper cursor position 3680 LD (HL),"solid space" The program will later recognise this as an end of paragraph marker 2180EA LD HL,#EA80 HL points to first byte (now blank) of L_TABLE 0603 LD B,#03 B:=number of spaces to indent start of next paragraph 3680 E_INDENT LD (HL),"solid space" The paragraph is indented with solid spaces which will not be deleted by the COMPRESS subroutine 23 INC HL Point to next byte 10FB DJNZ E_INDENT Repeat for three spaces FD7577 LD (L_CURSOR),L Store indented cursor position C37CEC JP DOWN Move cursor down below blank line, and exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - That was a mouthful, wasn't it? Our tale continues now with a table of addresses. This will be used later on by the program when it works out what subroutine it wants to call. ORG ED5E B6EC CTRL_TABLE DEFW CLEAR_LINE 40EC DEFW CLEAR_SCRN A7EC DEFW CAPS_LOCK 93EC DEFW EDIT 59EC DEFW LEFT 63EC DEFW RIGHT 7CEC DEFW DOWN 6DEC DEFW UP C1EC DEFW DELETE 2EED DEFW ENTER 1EEE DEFW YOURS B0EC DEFW COPY_13 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - And now for the moment you've all been waiting for - this is the program which ties all those subroutines together into one unified program. When you call the program from BASIC this is the address you must refer to. This is the very start of the proceedings. Note that because the program at one point relies on the value of the system variable SEED being fairly random then you shouldn't really use RANDOMIZE USR 60790. Instead, you can always use RANDOMIZE 0*USR 60790, or LET L=USR 60790, or even my own favourite little quirky oddity IF USR 60790 THEN (with nothing after the word THEN). Anyway, here it is - this is where it's all at! ORG ED76 218050 START LD HL,5080 This is the print position for the start of th 21st line on the screen 22865C LD (DFCCL),HL Set print position 212118 LD HL,#1821 Define coordinates as start of 1st line 228A5C LD (SPOSNL),HL Store coordinates FD363104 LD (DF_SZ),#04 Specify lower screen four lines wide FD360201 LD (TVFLAG),#01 Direct PRINT to lower part of screen 0680 LD B,#80 B:=number of bytes in lower part of screen 3E90 ST_LOOP LD A,"graphic A" D7 RST #10 Print border surrounding lower display 10FB DJNZ ST_LOOP CD40EC CALL CLEAR_SCRN Clear screen and set up cursors LOOP $ Wait and see ... - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The next bit is the main loop. Every time an action is carried out control will return to this point. The first things is to scan the keyboard. Let's see how it goes. ORG ED94 FDCB01AE LOOP RES 5,(FLAGS) Signal "last keystroke has been acted upon" CDE5EB CALL TRANSFER_L Display the result of the last action FDCB016E L_WAIT BIT 5,(FLAGS) Test for new keystroke 28FA JR Z,L_WAIT Wait until new key accepte (Note: this is not an infinite loop since the keyboard scanning procedure is done by the ROM's interrupt routine.) 3A085C LD A,(LAST_K) A:=INKEY$ (effectively) FEC7 CP "Symbol Shift Q" Symbol Shift Q is interpreted as a "paragraph reform" command 2836 JR Z,REFORM FEC9 CP "Symbol Shift W" Return to BASIC is "Symbol Shift W" pressed C8 RET Z FEC8 CP "Symbol Shift E" 2002 JR NZ,L_1 3E7F LD A,#7F Symbol Shift E becomes the copyright symbol FEAC L_1 CP "Symbol Shift I" 2002 JR NZ,L_2 3E80 LD A,"solid space" Symbol Shift I becomes the "graphic 8" character FE20 L_2 CP #20 300F JR NC,L_3 Jump forward unless a control character is given 87 ADD A,A Multiply code by two C656 ADD A,#56 The value 56 is CTRL_TABLE low minus 08 6F LD L,A 26ED LD H,#ED HL points to address of subroutine to call 5E LD E,(HL) 23 INC HL 56 LD D,(HL) DE:=address of subroutine to call EB EX DE,HL HL:=address of subroutine to call CD2C16 CALL (HL) Call the required subroutine 18CA JR LOOP and re-join the main loop Note: The 'instruction' CALL (HL) is not strictly speaking a true machine code instruction. It calls a 'subroutine' in the ROM consisting of the single instruction JP (HL). Think about it ... FE81 L_3 CP #81 380B JR C,L_4 Jump forward unless an 'E-mode' character is required 216A02 LD HL,#026A HL points to SYMBOL SHIFT LETTER table in ROM 45 LD B,L BC:=any large number EDB1 CPIR Locate given character 7D LD A,L D625 SUB #25 6F LD L,A HL points to required character 7E LD A,(HL) A:=required character CDD7EC CALL CHARACTER Add character to S_TABLE, etc. 18B6 JR LOOP - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The next section is the PARAGRAPH REFORM function, and it is this which makes our word processor so powerful. To change a word or phrase just use EDIT to get the appropriate line; make the change; and then without moving the upper cursor press 'Symbol Shift Q' and the whole paragraph will be re- constructed before your eyes. This is the machine code that performs the task. ORG EDDE 11C0EA REFORM LD DE,#EAC0 Point DE beyond all text in L_TABLE 1B R_FIND DEC DE Point to next byte 1A LD A,(DE) A:=next character in file FE20 CP "space" 28FA JR Z,R_FIND Loop back until non-space character found 13 INC DE Skip over character 13 INC DE Skip over following space 2AAF5C LD HL,(U_CURSOR) HL points to address of upper cursor 012000 LD BC,#0020 BC:=number of bytes in one line EDB0 LDIR Append line from upper screen to edit line CD00EB CALL COMPRESS Delete all unwanted spaces 21E0EA LD HL,#EAE0 Point HL just beyond L_TABLE 3E20 LD A,"space" 2B R_FIND_2 DEC HL Point to next byte BE CP (HL) Is it a space? 28FC JR Z,R_FIND_2 Loop back until non-space character found 7D LD A,L A:=low part of address of last character in file FEA0 CP #A0 2815 JR Z,R_EXIT Jump if text will fit on just one line CDFAEC CALL PROCESS_1 Process one line and transfer to upper screen 2AAF5C LD HL,(U_CURSOR) HL:=new address of upper cursor 7E LD A,(HL) FE80 CP "solid space" Test for end of paragraph 280A JR Z,R_EXIT Exit if end of paragraph found 1160EA LD DE,#EA60 DE:=lowest allowable cursor position A7 AND A ED52 SBC HL,DE 2802 JR Z,R_EXIT Exit if bottom of screen reached 18C7 JR REFORM Repeat for next line CD2EED R_EXIT CALL ENTER Treat as end of paragraph C394ED JP LOOP and back into main loop - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The next, final piece of machine code is somewhere in your head. What would you like a word processor to do that this one doesn't? This subroutine is YOUR subroutine. It will be carried out every time 'symbol shift' and 'caps shift' are pressed simultaneously. You could, for instance, then wait for a further key and offer a choice of different functions. Or maybe there's just one function you want to add. Whatever you choose - this one is truly up to you. It's all yours ... * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Editor's Note We would like to point out that Toni Baker's excellent book "Mastering Machine Code on Your Spectrum", recently published, is in no way connected with this series, which has been specially written for ZX Computing. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -- Another Fine Product transcribed by: Jim Grimwood (jimg@globalnet.co.uk), Weardale, England --