.........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
--
