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.