                      A timely remark



        John Ingleson explains how to program using

      REM statements, without affecting the run time.



The REM statement is probably the single most useful device

for simplifying the writing of programs. It may be used to

provide brief documentation within the program, perhaps the

only documentation that many programmers use. Names, dates,

descriptions, variables, subroutines and functions listed

at the beginning of a program are some of the things that

make life easier when called upon to modify or customise a

program written some time ago (or even yesterday).

  Labelling blocks of code, subroutines, data lists etc.,

with short explanations is also an invaluable tool in

making their use and logical structure apparent, giving the

writer clear reference points from which to work. The high-

lighting of comments with blank REM lines is perhaps a much

neglected device that is useful for saving eyestrain in

long program listings.

  However, the use of these techniques does have disadvan-

tages. The limits of memory may inhibit the use of detailed

documentation. There may simply not be enough room to write

or run the program despite, or rather because of, copious

useful notes. A program listing may easily consist of 25

per cent REM statements. If the program is relatively large

- say over 30K - then that can amount to a lot of unused

bytes at "run time".

  Where the constraints of memory size are not restrictive,

the size of an often used program while saving and loading

can prove tedious. One other complaint that may be cited

against the liberal use of REM statements is that of the

speed of program execution. While the operating system

"ignores" REM statements, it still takes a finite time to

do this. In the Spectrum, every time a subroutine or

function is called, the interpreter starts at the beginning

of the program and searches through until the relevant code

is found. Thus, REM statements, especially those at the

beginning (these usually being the bulkiest), are "ignored"

many times during execution, significantly slowing down the

speed at which the program runs.

  Using the Spectrum (a machine not noted for its lightning

fast speed in producing moving graphics in Basic), it would

clearly be an advantage to do without any REMs. However, it

is almost unthinkable to write any programs without them.

  How to resolve this dilemma? We could write the program,

including all our REMs, and then, when the program is

debugged and running to our requirements, simply delete all

the REMs by typing in the line numbers and then ENTER

(keeping a copy of the complete program with REMs for

future reference). This may seem a likely solution, until

it is tried in practice. Numb fingers, tired eyes, and

program lines that disappear without trace are some of the

pitfalls.

  But isn't this one of those dull routine jobs we keep

being told are the ideal tasks for a computer? Well, here

is a short machine code program that will allow you to

write as many REMs as you wish and then when your program

is complete - to strike them at a stroke. (Again - don't

forget to save a complete copy, REMs and all, for possible

future reference, modification and customisation.)

  First, let's review the way a Basic line is held in

memory (see diagram 1). The address of the start of the

first line number is stored by the Spectrum ROM at address

23635 and 23636. This is the system variable PROG. Similar-

ly, the address of the last byte of Basic program + 1 is

stored at address 23627 and 23628 (system variable VARS).

Both pairs of bytes are stored as Low byte - High byte.

___________________________________________________________



     ADDRESS (HEX)

       |  

       |  RAM DATA (HEX)

       v   v

     5C4B F2  VARS-+ Points to program end + 1

     5C4C 5C       |    (variables area)

 +-----------------+

 |   5C53 CB  PROG+  Points to program start

 |   5C54 5C      |

 | +--------------+                  LISTING

 | +>5CCB 00}

 |   5CCC 0A} Line number.......... 10

 |   5CCD 0B}

 |   5CCE 00} No. bytes in line

 |   5CCF F1  ...................... LET

 |   5CD0 61  ...................... a

 |   5CD1 3D  ...................... =

 |   5CD2 31  ...................... 1

 |   5CD3 0E  CHR$ 14 *

 |   5CD4 00}

 |   5CD5 00}

 |   5CD6 01} Number 1 dec.

 |   5CD7 00}

 |   5CD8 00}

 |   5CD9 0D  ENTER - new line

 |   5CDA 00}

 |   5CDB 14} Line number........... 20

 |   5CDC 05}

 |   5CDD 00} No. bytes in line

 |   5CDE EA  ...................... REM

 |          } Rest of program

 |   5CF1 0D  Last ENTER (end of program)

 |   .......

 +-->5CF2 80  Start of variables area



 Diagram 1

   illustrates how a program is held in Spectrum BASIC.

 * CHR$ 14 signifies number following in 5 byte format.

___________________________________________________________



  The first two bytes of a line hold the line number - High

byte first, then Low byte (the reverse of what we would

normally expect). The next two bytes hold the length of the

line (as we would expect - Low byte, High byte). Following

that, the actual code of the line, ending with 13 (the code

for ENTER). Then comes the next line number, and so on.

  Briefly, the routine works by checking the first piece of

code in a line number to see if it is a REM (code 234). If

it isn't it goes to the next line number - if it is, then

the remaining code (from the next line number to the end of

the program) is moved down memory, over-writing the REM

statement to be deleted. The end marker of the program

(system variable VARS) is then moved to its new position at

the end of the revised program to be moved down. The number

of bytes deleted is then stored at the end of the printer

buffer (for want of a less obtrusive location). The total

is needed at the end of the routine.

  This process is repeated until the end of the program is

detected, at which point a subroutine, held in the Spectrum

ROM, is called. This routine "tidies up" by reclaiming the

redundant bytes - left between the end of the revised

program and the end of the original one (echoes of the tail

end of the original code that has been repeatedly rewritten

down memory). This subroutine in ROM also calls another -

Pointers, which resets all the system poitners affected by

the changes. The code may be used as it is, to delete REMs

after line numbers and also line numbers with a space

following.

  A separate algorithm is needed if the last line is a REM,

because if BC is loaded with zero then BC will be decre-

mented to 65535 on the next cycle of LDIR. As BC is the

counter for LDIR then we will end up moving 65536 bytes

instead of none. In fact, we move the total number of bytes

in the line from beyond VARS to uphold the logic of the

subroutine MOVE, thereby setting VARS and STBYT (the total

of deleted bytes) correctly before returning from the

machine code program.

  If a machine code program is stored in a REM statement,

or a critical REM statement is to be kept in the program,

then the line may be "protected" by inserting an inverse

character (CHR$ 20 - Caps Shift 4) immediately following

the line number and before the REM (remember to remove the

inverse before using your machine code, as the position of

code will have moved in memory).

  Registers need not be saved by the routine, as the pro-

gram is unlikely to be used as a subroutine of another

program. Modifications may be made to delete REMs that

occur at the end of program lines.

  Hints - the whole program line will have to be checked

for a colon then REM (don't forget to exclude bytes that

hold data which might occur in the combination of the code

for a colon and then a REM). When deleting a colon REM, a

new algorithm will need to be developed to set VARS.

  NB. Program lines will be treated as blank and deleted if

the line number is followed by a space.

___________________________________________________________



[ This assembly code listing was also given in the article.

  Although the machine code is on the TZX which goes with

  this text, I've copied the listing as well, because of

  the instructive comments. ]



Memory Address

 |   Hex Code

 |    |        Assembler Source Line

 |    |         |   Label

 |    |         |    |   Operation 

 |    |         |    |    |   Operand           Comments

 v    v         v    v    v    v                 v

              0010 ;                        This  Routine  will

              0020 ;                        Delete  REMs & Blank

              0030 ;                        lines  in  BASIC

              0040 ;                        listings.  The code

              0050 ;                        is  relocatable  in

              0060 ;                               Memory

              0070 ;                        (o) John D. Ingleson

              0080 ;                               17/01/84

              0090 ;

5C53          0100 PROG  EQU  23635        Beginning of Program

5C4B          0110 VARS  EQU  23627        End of Program

19E8          0120 RCLM2 EQU  19E8H        Routine in ROM

58FE          0130 STBYT EQU  23550        Store number location

              0140 ;

              0150 ;**** ***  **********

              0160 ;

7F81          0170       ORG  32625        Location not critical

              0180 ;

7F71 210000   0190 START LD   HL,00        Zero No.bytes deleted

7F74 22FE5B   0200       LD   (STBYT),HL

              0210 ;

7F77 2A53BC   0220       LD   HL,(PROG)    HL holds location of

              0230 ;                       start of program

7F7A E5       0240 NEWLN PUSH HL

7F7B ED5B4B5C 0250       LD   DE,(VARS)

7F7F A7       0260       AND  A            End of program?

7F80 ED52     0270       SBC  HL,DE

7F82 306D     0280       JR   NC,RSTOR     Yes - Jump to RSTOR

              0290 ;

7F84 E1       0300       POP HL

7F85 23       0310       INC HL            Skip over line No.

7F86 23       0320       INC HL

              0330 ;

7F87 E5       0340       PUSH HL           Addr.of No.of bytes

7F88 D1       0350       POP  DE            into  DE

              0360 ;

7F89 4E       0370       LD   C,(HL)       BC to hold No. bytes

7F8A 23       0380       INC  HL            in line (excluding

7F8B 46       0390       LD   B,(HL)        2 line No. bytes)

              0400 ;

7F8C 23       0410       INC  HL           Look at next byte

7F8D 7E       0420       LD   A,(HL)       Code into A for check

              0430 ;

7F8E FEEA     0440       CP   234          Is it a REM? -

7F90 280D     0450       JR   Z,DEL        Yes - Then delete

              0460 ;

7F92 FE20     0470       CP   32           Space (Blank line)?

7F94 2809     0480       JR   Z,DEL        Yes - Then delete

              0490 ;

7F96 D5       0500       PUSH DE           Neither - Then add

7F97 E1       0510       POP  HL            the Addr. of No. of

              0520 ;                        bytes in line to the

7F98 A7       0530       AND  A             actual No. of bytes

7F99 ED4A     0540       ADC  HL,BC         in line + 2 to get

              0550 ;                        the location of the

7F98 23       0560       INC  HL            next  line  to  be

7F9C 23       0570       INC  HL                checked

              0580 ;

7F9D 18D8     0590       JR   NEWLN        Start again

              0600 ;

              0610 ;**** ***  **********

              0620 ;                          DELETE  ROUTINE

7F9F E5       0630 DEL   PUSH HL           Save current position

7FA0 A7       0640       AND  A             at REM  Present Addr

7FA1 ED4A     0650       ADC  HL,BC         + byt4es in line =

7FA3 E5       0660       PUSH HL            next line (Save it)

              0670 ;                        (Source for LDIR)

7FA4 ED5B485C 0680       LD   DE,(VARS)

7FA8 EB       0690       EX   HL,DE        VARS - Source = No.of

7FA9 A7       0700       AND  A             bytes to move  for

7FAA ED52     0710       SBC  HL,DE         LDIR op)

              0720 ;

7FAC AF       0730       XOR  A            Check for No. bytes

7FAD B4       0740       OR   H             to be moved = 0

7FAE B5       0750       OR   L             If so then the REM

7FAF 2834     0760       JR   Z,LREM        is in the last line:

              0770 ;                        new algorithm needed

7FB1 E5       0780       PUSH HL           No.bytes to move into

7FB2 C1       0790       POP  BC            BC

              0800 ;

7FB3 E1       0810       POP  HL           Source into HL

7FB4 D1       0820       POP  DE           Addr.of REM back into

7FB5 1B       0830       DEC  DE            DE... jump back over

7FB6 1B       0840       DEC  DE            Addr.of No. bytes in

7FB7 1B       0850       DEC  DE            line & line No. to

7FB8 1B       0860       DEC  DE            Destination in DE

              0870 ;

7FB9 D5       0880 MOVE  PUSH DE                MOVE ROUTINE

7FBA D5       0890       PUSH DE           Dest.on stack twice

7FBB E5       0900       PUSH HL            & Source

              0910 ;

7FBC EDB0     0920       LDIR              Overwrite REM with

              0930 ;                         rest of Program

7FBE E1       0940       POP  HL           ...Source

7FBF D1       0950       POP  DE           ...Destination

7FC0 A7       0960       AND  A            Source - Dest. = No.

7FC1 ED52     0970       SBC  HL,DE         bytes deleted. Save

7FC3 E5       0980       PUSH HL           for VARS calculation

              0990 ;

7FC4 11FE5B   1000       LD   DE,STBYT     Add  No. of  newly

7FC7 ED4BFE5B 1010       LD   BC,(STBYT)    deleted bytes (HL)to

7FCB A7       1020       AND  A             old total of deleted

7FCC ED4A     1030       ADC  HL,BC         bytes to get new

7FCE EB       1040       EX   DE,HL         total...

7FCF 73       1050       LD   (HL),E        store back in STBYT

              1060 ;

7FD0 23       1070       INC  HL

7FD1 72       1080       LD   (HL),D

              1090 ;

7DF2 E1       1100       POP  HL           VARS minus newly

7FD3 ED5B4B5C 1110       LD   DE,(VARS)     deleted bytes = new

7FD7 EB       1120       EX   DE,HL         value of VARS

7FD8 A7       1130       AND  A

7FD9 ED52     1140       SBC  HL,DE

              1150 ;

7FDB 114BBC   1160       LD   DE,VARS

7FDE EB       1170       EX   DE,HL

7FDF 73       1180       LD   (HL),E       New value back into

7FE0 23       1190       INC  HL            VARS

7FE1 72       1200       LD   (HL),D

7FE2 E1       1210       POP  HL           Dest. = start of 1st

7FE3 1895     1220       JR   NEWLN         line moved, go back

              1230 ;                        start check again

              1240 ;**** ***  **********

              1250 ;                         LAST REM ROUTINE

7FE5 E1       1260 LREM  POP  HL           ...Source

7FE6 D1       1270       POP  DE           Addr. of REM

              1280 ;

7FE7 1B       1290       DEC  DE           Jump over line No.

7FE8 1B       1300       DEC  DE            & No. bytes in line

7FE9 1B       1310       DEC  DE

7FEA 1B       1320       DEC  DE

              1330 ;

7FEB 03       1340       INC  BC           Bytes in line + 4

7FEC 03       1350       INC  BC            = No. bytes to move

7FED 03       1360       INC  BC

7FEE 03       1370       INC  BC

              1380 ;

7FEF 18C8     1390       JR   MOVE         Jump to MOVE

              1400 ;

              1410 ;**** ***  **********

              1420 ;                         RESTORE ROUTINE

7FF1 ED4BFE5B 1430 RSTOR LD   BC,(STBYT)   Deleted bytes in BC

7FF5 D1       1440       POP  DE           Restore stack

7FF6 C5       1450       PUSH BC           Save to print total

7FF7 2A4B5C   1460       LD   HL,(VARS)    Start RECLM2 at VARS

7FF8 CDE819   1470       CALL RCLM2        Subroutine in ROM

7FFD C1       1480       POP  BC           PRINT USR for total

7FFE C9       1490 END   RET                No.of deleted bytes

              1500 ;

              1510 ;**** ***  **********

              1520 ;

              1530       END

END    7FFE

RSTOR  7FF1

LREM   7FE5

MOVE   7FB9

DEL    7F9F

NEWLN  7F7A

START  7F71

STBYT  5BFE

RCLM2  19E8

VARS   5C4B

PROG   5C53

___________________________________________________________



Enter this program and SAVE it. Then RUN it to load the

machine code into memory if correct. Then run the machine

code by - PRINT USR 32625 which will PRINT the number of

bytes deleted and when the BASIC program is LISTed the REMs

and blank lines will be seen to have been deleted.



[ The program which was listed here is on the TZX as

  "REMDelete". ]



To SAVE the CODE for use just-



 SAVE "REMDELETE" CODE nnnn,142



    where nnnn is any suitable address from which to run

your machine code.

[ The attentive reader will have spotted that this is

  wrong. You can't save code from an address where it has

  not been POKEd to. Instead, assuming you have not changed

  the BASIC program to POKE to a different address, you

  must always SAVE "remdelete"CODE 32625,142 - and this is

  precisely what has already been done for the "remdelete"

  CODE file present on the TZX.

  The following lines _are_ correct. Note, however, that

  the routine is relocatable, which means that "nnnn" does

  not have to be the same as the address at which the code

  has been saved. You can, therefore, load the code from

  the TZX at a different address than 32625. This, in turn,

  has the consequence that this routine is suitable for 48K

  Spectrums as well as for 16K ones. ]



Then to use the code -



CLEAR nnnn-1

LOAD ""CODE nnnn



And to execute the code -



PRINT USR nnnn



- which will PRINT the number of bytes of REMs deleted.