Building A Program
Using what we've learned so far, and adding a couple of new techniques, let's build a useful program. This example will demonstrate many of the techniques we've discussed and will also show some of the thought processes involved in writing ML.
Among the computer's more impressive talents is searching. It can run through a mass of information and find something very quickly. We can write an ML routine which looks through any area of memory to find matches with anything else. If your BASIC doesn't have a FIND command or its equivalent, this could come in handy. Based on an idea by Michael Erperstorfer published in COMPUTE! Magazine, this ML program will report the line numbers of all the matches it finds.
Safe Havens
Before we go through some typical ML program-building methods, let's clear up the "where do I put it?" question. ML can't just be dropped anywhere in memory. When the Simple Assembler asks "Starting Address?", you can't give it any address you want to. RAM is used in many ways. There is always the possibility that a BASIC program might be residing in part of it (if you are combining ML with a BASIC program). Or BASIC might use part of RAM to store arrays or variables. During execution, these variables might write (POKE) into the area that you placed your ML program, destroying it. Also, the operating system, the disk operating system, cassette/disk loads, printers - they all use parts of RAM for their activities. There are other things going on in the computer beside your hard-won ML program.
Obviously, you can't put your ML into ROM addresses. That's impossible. Nothing can be POKEd into those addresses. The 64 is an exception to this. You can POKE into ROM areas because a RAM exists beneath the ROM. Refer to the Programmer's Reference Guide or see Jim Butterfield's article on 64 architecture (COMPUTE! Magazine, January 1983) for details.
Where to put ML? There are some fairly safe areas.
If you are using Applesoft in ROM, 768 to 1023 ($0300 to $03FF) is safe. Atari's page six, 1536 to 1791 ($0600 to $06FF) is good. The 64 and VIC's cassette buffer at 828 to 1019 ($033C to $03FB) are good if you are not LOADing or SAVEing from tape.
The PET/CBM makes provision for a second cassette unit. In theory, it would be attached to the computer to allow you to update files or make copies of programs from Cassette #1 to Cassette #2. In practice, no one has mentioned finding a use for a second cassette drive. It is just as easy to use a single cassette for anything that a second cassette could do. As a result, the buffer (temporary holding area) for bytes streaming in from the second cassette unit is very safe indeed. No bytes ever flow in from the phantom unit so it is a perfect place to put ML.
The "storage problem" can be solved by knowing the free zones, or creating space by changing the computer's understanding of the start or end of BASIC programs. When BASIC is running, it will set up arrays and strings in RAM memory. Knowing where a BASIC program ends is not enough. It will use additional RAM. Sometimes it puts the strings just after the program itself. Sometimes it builds them down from the "top of memory," the highest RAM address. Where are you going to hide your ML routine if you want to use it along with a BASIC program? How are you going to keep BASIC from overwriting the ML code?
Misleading The Computer
If the ML is a short program you can stash it into the safe areas listed above. Because these safe areas are only a couple of hundred bytes long, and because so many ML routines want to use that area, it can become crowded. Worse yet, we've been putting the word "safe" in quotes because it just isn't all that reliable. Apple uses the "safe" place for high-res work, for example. The alternative is to deceive the computer into thinking that its RAM is smaller than it really is. This is the real solution.
Your ML will be truly safe if your computer doesn't even suspect the existence of set-aside RAM. It will leave the safe area alone because you've told it that it has less RAM than it really does. Nothing can overwrite your ML program after you misdirect your computer's operating system about the size of its RAM memory. There are two bytes in zero page which tell the computer the highest RAM address. You just change those bytes to point to a lower address.
These crucial bytes are 55 and 56 ($37,38) in the 64 and VIC. They are 52,53 ($34,35) in PET/CBM Upgrade and 4.0 BASIC. In the PET with Original ROM BASIC, they are 134,135 ($86,87). The Apple uses 115,116 ($73,74), and you lower the Top-of-BASIC pointer just as you do in Commodore machines.
The Atari does something similar, but with the bottom of RAM. It is easier with the Atari to store ML just below BASIC than above it. Bump up the "lomem" pointer to make some space for your ML. It's convenient to start ML programs which are too long to fit into page six ($0600-06FF) at $11700 and then put this address into lomem. The LSB and MSB are reversed, of course, as the 6502 requires its pointers to be like this:
$02E7 00
$02E8 1F
$02E7,8 is Atari's low memory pointer. You should set up this pointer (LDA $00, STA $02E7, LDA #$117, STA $02E8) as part of your ML program. Following that pointer setup, JMP $A000 which initializes BASIC. If you are not combining ML with a BASIC program, these preliminary steps are not necessary.
Safe Atari zero page locations include $00-04, $CB-D0, $D4-D9 (if floating point numbers are not being used); $0400 (the printer and cassette buffer), $0500-05717 (free), $0580-05FF (if floating point and the Editor are not being used), $0600-06FF (free) are also safe. No other RAM from $0700 (Disk Operating System) to $9FFF or $BFFF is protected from BASIC.
To repeat: address pointers such as these are stored in LSB, MSB order. That is, the more significant byte comes second (this is the reverse of normal, but the 6502 requires it of address pointers). For example, $8000, divided between two bytes in a pointer, would look like this:
0073 00
0074 80
As we mentioned earlier, this odd reversal is a peculiarity of the 6502 that you just have to get used to. Anyway, you can lower the computer's opinion of the top-of-RAM-memory, thereby making a safe place for your ML, by changing the MSB. If you need one page (256 bytes): POKE 116, PEEK (116)-1 (Apple). For four pages (1024 bytes) on the Upgrade and 4.0 PETs: POKE 53, PEEK (53) -4. Then your BA or start of assembling could begin at (Top-of-RAM-255 or Top-of-RAM-1023, respectively. You don't have to worry much about the LSB here. It's usually zero. If not, take that into account when planning where to begin storage of your object code.
Program 8-1. PET Search
(4.0 BASIC Version).
0010 |
; SEARCH THROUGH
BASIC |
||
0015 |
; PET 4.0 VERSION |
||
0016 |
;----------------------------------------------------- |
||
0017 |
; -0-
DEFINE VARIABLES BY GIVING THEM LABELS. |
||
0018 |
; |
||
0020 |
L1L |
.DE $BA
;STORE THESE IN |
|
0030 |
L2L |
.DE $BC
;UNUSED ZERO PG AREA |
|
0040 |
FOUND |
.DE $36 |
|
0050 |
BASIC |
.DE $0400 |
|
0060 |
PRINT |
.DE $FFD2 ;
PRINT A CHAR. |
|
0070 |
PLINE |
.DE $CF7F ;
PRINT LINE# |
|
0100 |
.BA $0360 ;
2ND CASSETTE BUFFER |
||
0110 |
.OS |
||
0120 |
;------------------------------------------------------ |
||
0121 |
; -0- INITIALIZE
POINTERS. |
||
0130 |
; |
||
0140 |
; |
||
0360- AD 01 04 |
0150 |
LDA BASIC+1 ;GET
ADDR OF NEXT |
|
0363- 85 BA |
0160 |
STA *L1L
;BASIC LINE |
|
0365- AD 02 04 |
0170 |
LDA BASIC+2 |
|
0368- 85 BB |
0180 |
STA *L1L+l |
|
0181 |
;------------------------------------------------------ |
||
0182 |
; -0- SUBROUTINE
TO CHECK FOR 2 ZEROS. IF WE DON'T |
||
0183 |
; FIND THEM,
WE ARE NOT AT THE END OF THE PROGRAM. |
||
0184 |
; |
||
036A- A0 00 |
0190 |
READLINE |
LDY #$00 |
036C- Bl BA |
0200 |
LDA (L1L),Y |
|
036E- D0 06 |
0210 |
BNE GO.ON
; NOT END OF LINE |
|
0370- C8 |
0220 |
INY |
|
0371- Bl BA |
0230 |
LDA (L1L),Y ;
00 00 = END OF PROG. |
|
0373- D0 01 |
0240 |
BNE GO.ON |
|
0375- 60 |
0250 |
END |
RTS
;RETURN TO BASIC |
0251 |
;------------------------------------------------------ |
||
0252 |
; -0- SUBROUTINE
TO UPDATE POINTERS TO THE NEXT LINE |
||
0253 |
; AND
STORE THE CURRENT LINE NUMBER IN CASE WE |
||
0254 |
; FIND
A MATCH AND NEED TO PRINT THE LINE #. |
||
0255 |
; ALSO,
WE ADD 4 TO THE CURRENT LINE POINTER SO THAT |
||
0256 |
; WE
ARE PAST THE LINE # AND "POINTER-TO-NEXT-LINE" |
||
0257 |
; INFORMATION.
WE ARE THEN POINTING AT THE 1ST CHAR. |
||
0258 |
; IN
THE CURRENT LINE AND CAN COMPARE IT TO THE SAMPLE. |
||
0259 |
; |
||
0376- A0 00 |
0260 |
GO.ON |
LDY #$00 |
0378- Bl BA |
0270 |
LDA (L1L),Y
; GET NEXT LINE |
|
037A- 85 BC |
0280 |
STA *L2L
; ADDRESS AND |
|
037C- C8 |
0290 |
INY
; STORE IT IN L2L |
|
037D- Bl BA |
0300 |
LDA (L1L),Y |
|
037F- 85 BD |
0310 |
STA *L2L+1 |
|
0381- C8 |
0320 |
INY |
|
0382- B1 BA |
0330 |
LDA (L1L),Y
; PUT LINE # |
|
0384- 85 36 |
0340 |
STA *FOUND
; IN STORAGE TOO |
|
0386- C8 |
0350 |
INY
; IN CASE IT |
|
0387- Bl BA |
0360 |
LDA (L1L),Y
; NEEDS TO BE |
|
0389- 85 37 |
0370 |
STA *FOUND+l
;PRINTED OUT LATER |
|
038B- A5 BA |
0380 |
LDA *L1L |
|
038D- 18 |
0390 |
CLC ;
MOVE FORWARD TO 1ST |
|
038E- 69 04 |
0400 |
ADC #$04
; PART OF BASIC TEXT |
|
0390- 85 BA |
0410 |
STA *L1L
; (PAST LINE # AND |
|
0392- A5 BB |
0420 |
LDA *L1L+1
; OF NEXT LINE) |
|
0394- 69 00 |
0430 |
ADC #$00 |
|
0396- 85 BB |
0440 |
STA *L1L+1 |
|
0441 |
;------------------------------------------------------ |
||
0442 |
; -0- SUBROUTINE
TO CHECK FOR ZERO (LINE IS FINISHED?) |
||
0443 |
; AND THEN CHECK 1ST CHARACTER IN BASIC LINE AGAINST | ||
0444 |
; 1ST CHARACTER IN SAMPLE STRING AT LINE 0:. IF THE | ||
0445 |
; 1ST
CHARACTERS MATCH, WE MOVE TO A FULL STRING |
||
0446 |
; COMPARISON
IN THE SUBROUTINE CALLED "SAME." IF 1ST |
||
0447 |
; CHARS.
DON'T MATCH, WE RAISE THE "Y" COUNTER AND |
||
0448 |
; CHECK
FOR A MATCH IN THE 2ND CHAR. OF THE CURRENT |
||
0449 |
; BASIC
LINE'S TEXT. |
||
0450 |
; |
||
0398- A0 00 |
0451 |
LDY #$00 |
|
039A- Bl BA |
0460 |
LOOP |
LDA (L1L),Y |
039C- F0 1C |
0470 |
BEQ STOPLINE
; ZERO = LINE FINISHED |
|
039E- CD 06 04 |
0480 |
CMP BASIC+6
; SAME AS 1ST SAMPLE CHAR? |
|
03Al- F0 04 |
0490 |
BEQ SAME
; YES? CHECK WHOLE STRING |
|
03A3- C8 |
0500 |
INY
; NO? CONTINUE SEARCH |
|
03A4- 4C 9A 03 |
0510 |
JMP LOOP |
|
0511 |
;------------------------------------------------------ |
||
0512 |
; -0- SUBROUTINE
TO LOOK AT EACH CHARACTER IN BOTH THE |
||
0513 |
; SAMPLE
(LINE 0) AND THE TARGET (CURRENT LINE) TO |
||
0514 |
; SEE
IF THERE IS A PERFECT MATCH. Y KEEPS TRACK OF |
||
0515 |
; TARGET.
X INDEXES SAMPLE. IF WE FIND A MISMATCH |
||
0516 |
; BEFORE
A LINE-END ZERO, WE FALL THROUGH TO LINE |
||
0517 |
; 590
AND JUMP BACK UP TO 460 WHERE WE CONTINUE ON |
||
0518 |
;
LOOKING FOR 1ST CHAR MATCHES IN THE CURRENT LINE. |
||
0519 |
; |
||
03A7- A2 00 |
0520 |
SAME |
LDX #$00
;COMPARE SAMPLE TO |
03A9- E8 |
0530 |
COMPARE |
INX
;TARGET |
03AA- C8 |
0540 |
INY |
|
03AB- BD 06 04 |
0550 |
LDA BASIC+6,X |
|
03AE- F0 07 |
0560 |
BEQ PERFECT
; LINE ENDS SO PRINT |
|
03B0- D1 BA |
0570 |
CMP (L1L),Y |
|
03B2- F0 F5 |
0580 |
BEQ COMPARE ;
CONTINUE COMPARE |
|
03B4- 4C 9A |
0590 |
JMP LOOP
;NO MATCH |
|
03B7- 20 C5 03 |
0600 |
PERFECT |
JSR PRINTOUT |
0601 |
;------------------------------------------------------ |
||
0602 |
; -0- SUBROUTINE
TO REPLACE "CURRENT LINE" POINTER |
||
0603 |
; WITH THE
"NEXT LINE" POINTER WE SAVED IN THE SUBROUT |
||
0604 |
; STARTING
AT LINE 260. |
||
0605 |
; THEN JUMP
BACK. TO THE START WITH THE CHECK FOR THE |
||
0606 |
; END-OF-PROGRAM
DOUBLE ZERO. THIS IS THE LAST SUBROUT |
||
0607 |
; IN THE MAIN
LOOP OF THE PROGRAM |
||
0608 |
; |
||
03BA- A5 BC |
0610 |
STOPLINE |
LDA *L2L
;TRANSFER NEXT LINE |
03BC- 85 BA |
0620 |
STA *L1L
; ADDRESS POINTER TO |
|
03BE- A5 BD |
0630 |
LDA *L2L+1
; CURRENT LINE POINTER |
|
03C0- 85 BB |
0640 |
STA *L1L+l
; TO GET READY TO READ |
|
03C2- 4C 6A 03 |
0650 |
JMP READLINE ;THE
NEXT LINE. |
|
0651 |
;------------------------------------------------------ |
||
0652 |
; -0- SUBROUTINE
TO PRINT OUT A BASIC LINE NUMBER. |
||
0653 |
; IN
MICROSOFT IT TAKES THE NUMBER STORED IN $36,37 |
||
0654 |
; AND
THE ROM ROUTINE PRINTS THE NUMBER AT THE NEXT |
||
0655 |
; CURSOR
POSITION ON SCREEN. THEN WE PRINT A BLANK |
||
0656 |
; SPACE
AND RETURN TO LINE 610 TO CONTINUE ON WITH |
||
0657 |
;
THE MAIN LOOP AND FIND MORE MATCHES. |
||
0658 |
; |
||
03C5- 20 7F CF |
0660 |
PRINTOUT |
JSR PLINE
; ROM ROUTINE PRINTS |
0661 |
; A LINE NUMBER FROM THE
VALUES FOUND |
||
0662 |
; IN "FOUND" ($36,37) |
||
03C8- A9 20 |
0670 |
LDA #$20
;PRINT A BLANK |
|
03CA- 20 D2 FF |
0680 |
JSR PRINT
; SPACE BETWEEN #S |
|
03CD- 60 |
0690 |
RTS |
|
0691 |
;------------------------------------------------------ |
||
0692 |
; |
||
0700 |
.EN |
--- LABEL FILE: ---
BASIC =0400 |
COMPARE =03A9 |
END =0375 |
FOUND =0036 |
GO.ON =0376 |
L1L =00BA |
L2L =00BC |
LOOP =039A |
PERFECT =03B7 |
PLINE =CF7F |
PRINT =FFD2 |
PRINTOUT =03C5 |
READLINE =036A |
SAME =03A7 |
STOPLINE =03BA |
Building The Code
Now we return to the subject at hand - building an ML program. Some people find it easiest to mentally break a task down into several smaller problems and then weave them into a complete program. That's how we'll look at our search program. (See Program 8-1.)
For this exercise, we can follow the PET/CBM 4.0 BASIC version to see how it is constructed. All the versions (except Atari's) are essentially the same, as we will see in a minute. The only differences are in the locations in zero page where addresses are temporarily stored, the "start-of-BASIC RAM" address, the routines to print a character and to print a line number, and the RAM where it's safe to store the ML program itself. In other words, change the defined variables between lines 20 and 100 in Program 8-1 and you can use the program on another computer.
We will build our ML program in pieces and then tie them all together at the end. The first phase, as always, is the initialization. We set up the variables and fill in the pointers. Lines 20 and 30 define two, two-byte zero page pointers. L1L is going to point at the address of the BASIC line we are currently searching through. L2L points to the starting address of the line following it.
Microsoft BASIC stores four important bytes just prior to the start of the code in a BASIC line. Take a look at Figure 8-1. The first two bytes contain the address of the next line in the BASIC program. The second two bytes hold the line number. The end of a BASIC line is signaled by a zero. Zero does not stand for anything in the ASCII code or for any BASIC command. If there are three zeros in a row, this means that we have located the "top," the end of the BASIC program. (The structure of Atari BASIC is significantly different. See Figure 8-2.)
But back to our examination of the ML program. In line 40 is a definition of the zero page location which holds a two-byte number that Microsoft BASIC looks at when it is going to print a line number on the screen. We will want to store line numbers in this location as we come upon them during the execution of our ML search program. Each line number will temporarily sit waiting in case a match is found. If a match is found, the program will JSR to the BASIC ROM routine we're calling "PLINE," as defined in line 70. It will need the "current line number" to print to the screen.
Line 50 establishes that BASIC RAM starts at $0400 and line 60 gives the address of the "print the character in the accumulator" ROM routine. Line 100 says to put the object code into the PET's (all BASIC versions) second cassette buffer, a traditional "safe" RAM area to store short ML programs. These safe areas are not used by BASIC, the operating system (OS), or, generally, by monitors or assemblers. If you are working with an assembler or monitor, however, and keep finding that your object code has been messed up - suspect that your ML creating program (the monitor or assembler) is using part of your "safe" place. They consider it safe too. If this should happen, you'll have to find a better location.
Refer to Program 8-1 to follow the logic of constructing our Microsoft search program. The search is initiated by typing in line zero followed by the item we want to locate. It might be that we are interested in removing all REM statements from a program to shorten it. We would type 0:REM and hit RETURN to enter this into the BASIC program. Then we would start the search by a SYS to the starting address of the ML program. In the PET 4.0 version of Program 8-1, it would be SYS 864 (hex $0360).
By entering the "sample" string or command into the BASIC program as line zero, we solve two problems. First, if it is a string, it will be stored as the ASCII code for that string, just as BASIC stores strings. If it is a keyword like REM, it will be translated into the "tokenized," one-byte representation of the keyword, just as BASIC stores keywords. The second problem this solves is that our sample is located in a known area of RAM. By looking at Figure 8-1, you can tell that the sample's starting address is always the start of BASIC plus six. In Program 8-1 that means 0406 (see line 550).
Set Up The Pointers
We will have to get the address of the next line in the BASIC program we are searching. And then we need to store it while we look through the current line. The way that BASIC lines are arranged, we come upon the link to the next line's address and the line number before we see any BASIC code itself. Therefore, the first order of business is to put the address of the next line into LIL. Lines 150 through 180 take the link found in start-of-BASIC RAM (plus one) and move it to the storage pointer "L1L."
Next, lines 190 to 250 check to see if we have reached the end of the BASIC program. It would be the end if we had found two zeros in a row as the pointer to the next line's address. If it is the end, the RTS sends us back to BASIC mode.
The subroutine in lines 260 through 440 saves the pointer to the following line's address and also the current line number. Note the double-byte addition in lines 390-440. Recall that we CLC before any addition. If adding four to the LSB (line 400) results in a carry, we want to be sure that the MSB goes up by one during the add-with-carry in line 430. It might seem to make no sense to add a zero in that line. What's the point? The addition is with carry; in other words, if the carry flag has been set up by the addition of four to the LSB in line 400, then the MSB will go up by one. The carry will make this happen.
First Characters
It's better to just compare the first character in a word against each byte in the searched memory than to try to compare the entire sample word. If you are looking for MEM, you don't want to stop at each byte in memory and see if M-E-M starts there. Just look for M's. When you come upon a M, then go through the full string comparison. If line 490 finds a first-character match, it transfers the program to "SAME" (line 520) which will do the entire comparison. On the other hand, if the routine starting at line 451 comes upon a zero (line 470), it knows that the BASIC line has ended (they all end with zero. It then goes down to "STOPLINE" (line 610) which puts the "next line" address pointer into the "current line" pointer and the whole process of reading a new BASIC line begins anew.
If, however, a perfect match was found (line 560 found a zero at the end of the 0:REM line, showing that we had come to the end of the sample string) - we go to "PERFECT" and it makes a JSR to print out the line number (line 660). That subroutine bounces back (RTS) to "STOPLINE" which replaces the "current line" (L1L) pointer with the "next line" pointer (L2L). Then we JMP back to "READLINE" which, once again, pays very close attention to zeros to see if the whole BASIC program has ended with double zeros. We have returned to the start of the main loop of this ML program.
This sounds more complicated than it is. If you've followed this so far, you can see that there is enormous flexibility in constructing ML programs. If you want to put the "STOPLINE" segment earlier than the "SAME" subroutine - go ahead. It is quite common to see a structure like this:
INITIALIZATION
LDA #15
STA $83
MAIN LOOP
START JSR 1
JSR 2
JSR 3
BEQ START (until some index runs
out)
RTS
(to BASIC)
SUBROUTINES
1
2 (each ends with
RTS back to the MAIN LOOP)
3
DATA
Table 1
Table 2
Table 3
The Atari FIND Utility
The second source listing, Program 8-2, adds a FIND command to Atari BASIC. You access it with the USR command. It is written to assemble in page six (1536 or $0600) and is an example of a full-blown assembly. You'll need the assembler/ editor cartridge to type it in.
After you've entered it, enter "ASM" to assemble it into memory. After it is finished, use the SAVE command to store the object (executable ML) code on tape or disk. Use:
SAVE#C: > 0600,067E for tape
SAVE#D:FIND.OBJ < 0600 067E
for disk
You can then put the BASIC cartridge in and enter the machine language with the BASIC loader program, or with the L command of DOS.
Using FIND from BASIC is simple. Say you want to search a master string, A$ for the substring "hello". If B$ contains "hello", the USR call would look like:
POS=USR (1536,ADR(A$),LEN(A$),ADR(B$),LEN(B$)
)
POS will contain the position of the match. It will be a memory location within the ADRress of A$. To get the character position within A$, just use POS-ADR(A$)+1. If the substring (B$) is not found, POS will be zero.
It's easy to add commands like this to Atari BASIC. Also see "Getting The Most Out Of USR" in the November 1982 issue of COMPUTE! Magazine (p. 100).
64, Apple, & VIC Versions
Versions of the search routine for the Commodore 64 and VIC-20 and the Apple II are provided as BASIC loader programs. Remember from Chapter 2 that a loader is a BASIC program which POKEs a machine language program (stored in DATA statements) into memory. Once you have entered and run the BASIC programs, you can examine the ML programs using a disassembler. (See Appendix D.)
These versions are similar to the PET Version outlined in Program 8-1. The characters to be searched for are typed in line 0. To start the search in the 64 version (Program 8-3), type SYS 40800. Use CALL 768 to activate the Apple version (Program 8-4). The VIC version (Program 8-5) is activated with SYS 828.
As your skills improve, you will likely begin to appreciate, and finally embrace, the extraordinary freedom that ML confers on the programmer. Learning it can seem fraught with obscurity and rules. It can even look menacing. But there are flights you will soon be taking through your computer. Work at it. Try things. Learn how to find your errors. It's not circular - there will be considerable advances in your understanding. One day, you might be able to sit down and say that you can combine BASIC with ML and do pretty much anything you want to do with your machine.
Program 8-2.
0100 |
;========================; |
||
0110 |
; FIND Utility
; |
||
0120 |
; Substring
Search ; |
||
0130 |
; for Atari
BASIC ; |
||
0140 |
; Completely
relocatable ; |
||
0150 |
;========================; |
||
0160 |
; |
||
0170 |
; |
||
0180 |
;Variables
in zero page for speed |
||
0190 |
; |
||
00CB |
0200 |
SADRL |
=$CB ;Address |
00CC |
0210 |
SADRH |
=$CC ;of
search |
00CD |
0220 |
SLENL |
=$CD ;Length
of |
00CE |
0230 |
SLENH |
=$CE ;search
space |
0240 |
; |
||
00CF |
0250 |
FNDL |
=$CF ;Search
address |
00D0 |
0260 |
FNDH |
=$D0 ;and |
00D1 |
0270 |
FNDLEN |
=$Dl ;length
0280 ; |
0280 |
; |
||
00D2 |
0290 |
FIRSTCHAR |
=$D2 |
00D3 |
0300 |
SINDEX |
=$D3 |
00D4 |
0310 |
FR0 |
=$D4 ;Return |
00D6 |
0320 |
FINDEX |
=$D6 ;Source
index |
00D7 |
0330 |
TADRL |
=$D7 ;Temp
addr |
00D8 |
0340 |
TADRH |
=$D8 |
00D9 |
0350 |
ENDLOOP |
=D9 |
0360 |
; |
||
0370 |
;Syntax documentation |
||
0380 |
; |
||
0390 |
;FIND:Find
Text |
||
0400 |
;X=USR(FIND,A,B,C,D) |
||
0410 |
;FIND:Address
of utility (1536) |
||
0420 |
;A: Where
to start search |
||
0430 |
;B: Where
to quit searching |
||
0440 |
;C: Search
string address |
||
0450 |
;D: Length
of search string |
||
0460 |
;X: Position
found (=0 if no match) |
||
0000 |
0470 |
*= $0600 |
|
0480 |
;------------------------------------ |
||
0490 |
;This portion
sets up the parameters |
||
0500 |
;for the search
by pulling the values |
||
0510 |
;passed by
BASIC off the stack |
||
0520 |
; |
||
0530 |
FIND |
||
0600 68 |
0540 |
PLA
;Count byte |
|
0601 68 |
0550 |
PLA
;hi byte, Source start |
|
0602 85CC |
0560 |
STA SADRH |
|
0604 68 |
0570 |
PLA
;lo byte, Source start |
|
0605 85CB |
0580 |
STA SADRL |
|
0607 68 |
0590 |
PLA
;hi byte, Source end |
|
0608 85CE |
0600 |
STA SLENH |
|
060A 68 |
0610 |
PLA
;lo byte, Source end |
|
060B 85CD |
0620 |
STA SLENL |
|
060D 68 |
0630 |
PLA
;hi byte, Search string |
|
060E 85D0 |
0640 |
STA FNDH |
|
0610 68 |
0650 |
PLA
;lo byte, Search string |
|
0611 85CF |
0660 |
STA FNDL |
|
0613 68 |
0670 |
PLA
;hi byte, Search length |
|
0680 |
;Ignore it |
||
0614 68 |
0690 |
PLA
;lo byte, Search length |
|
0615 85D1 |
0700 |
STA FNDLEN |
|
0710 |
; |
||
0720 |
;-------------------------- |
||
0730 |
;This is the
main loop. We |
||
0740 |
;search through
the search space |
||
0750 |
;looking for
the first character |
||
0760 |
;of the search
string. We |
||
0770 |
;look through
entire 256-byte |
||
0780 |
;blocks. If
the first character |
||
0790 |
;is found,
we exit to a full |
||
0800 |
;string comparison
routine. |
||
0810 |
; |
||
0820 |
;If the string
is never found, |
||
0830 |
;we just return
a zero to BASIC |
||
0840 |
; |
||
0617 A000 |
0850 |
LDY #0 |
|
0619 BlCF |
0860 |
LDA (FNDL),Y
;Set up first |
|
061B 85D2 |
0870 |
STA FIRSTCHAR ;comparison |
|
0880 |
; |
||
061D A6CE |
0890 |
LDX SLENH
;Less than 255 |
|
061F F018 |
0900 |
BEQ SHORT
;bytes? |
|
0910 |
NXTSRCH |
||
0621 A9FF |
0920 |
LDA #255
;Select end |
|
0930 |
SEARCH2 |
||
0623 85D9 |
0940 |
STA ENDLOOP |
|
0625 A000 |
0950 |
LDY #0 |
|
0960 |
SEARCHLOOP |
||
0627 BlCB |
0970 |
LDA (SADRL),Y |
|
0629 C5D2 |
0980 |
CMP FIRSTCHAR ;Found
a match? |
|
062B F017 |
0990 |
BEQ FOUND1
;yes |
|
1000 |
NOTFOUND |
||
062D C8 |
1010 |
INY ;no |
|
062E C4D9 |
1020 |
CPY ENDLOOP |
|
0630 D0F5 |
1030 |
BNE SEARCHLOOP ;continue |
|
1040 |
; |
||
0632 E6CC |
1050 |
INC SADRH
;Next block |
|
0634 CA |
1060 |
DEX
;Done? |
|
0635 3006 |
1070 |
BMI EXIT
;yes |
|
0637 D0E8 |
1080 |
BNE NXTSRCH
;nope |
|
1090 |
SHORT |
||
0639 A5CD |
1100 |
LDA SLENL
;Set up last |
|
063B D0E6 |
1110 |
BNE SEARCH2 ;scan |
|
1120 |
EXIT |
||
063D A900 |
1130 |
LDA #0
;return |
|
063F 85D4 |
1140 |
STA FRO
;=0 |
|
0641 85D5 |
1150 |
STA FRO+1
;?no strin |
|
0643 60 |
1160 |
RTS
;found |
|
1170 |
; |
||
1180 |
;------------------------------ |
||
1190 |
;Here is where
we check for a |
||
1200 |
;full match,
starting with the |
||
1210 |
;second character
of the search string |
||
1220 |
;We have to
use two "pseudo" registers |
||
1230 |
;in memory,
since the same Y register |
||
1240 |
;is needed
to access both areas of memory |
||
1250 |
;(search
space and search string) |
||
1260 |
; |
||
1270 |
FOUND 1 |
||
0644 84D4 |
1280 |
STY FRO
;Save Y |
|
0646 84D3 |
1290 |
STY SINDEX
;Source index |
|
0648 A001 |
1300 |
LDY #1 |
|
064A 84D6 |
1310 |
STY FINDEX
;Find index |
|
1320 |
; |
||
1330 |
;We use a
temporary address, since we don't want |
||
1340 |
;to change
the address in SADR (so we can continue the |
||
1350 |
;search
if no match found) |
||
1360 |
; |
||
064C A5CB |
1370 |
LDA SADRL
;Copy to |
|
064E 85D7 |
1380 |
STA TADRL
;temp addr |
|
0650 A5CC |
1390 |
LDA SADRH |
|
0652 85D8 |
1400 |
STA TADRH |
|
1410 |
; |
||
1420 |
CONTSRCH |
||
1430 |
; |
||
1440 |
;As long as
each character matches, we |
||
1450 |
;continue
to compare until we get a failed comparison |
||
1460 |
;or reach
the end of the search string, |
||
1470 |
;which indicates
a match |
||
1480 |
; |
||
0654 A4D6 |
1490 |
LDY FINDEX |
|
0656 C4D1 |
1500 |
CPY FNDLEN
;Past end? |
|
0658 F016 |
1510 |
BEQ FOUND2
;yes-matchl |
|
065A BlCF |
1520 |
LDA (FNDL),Y
;Character n |
|
065C E6D6 |
1530 |
INC FINDEX
;no, increment |
|
065E A4D3 |
1540 |
LDY SINDEX
;Compare to |
|
0660 C8 |
1550 |
INY
;source |
|
0661 D002 |
1560 |
BNE SKIPINC
;Hit page bound? |
|
0663 E6D8 |
1570 |
INC TADRH |
|
1580 |
SKIPINC |
||
0665 84D3 |
1590 |
STY SINDEX
;Update |
|
0667 DlD7 |
1600 |
CMP (TADRL),Y ;equal
so far? |
|
0669 F0E9 |
1610 |
BEQ CONTSRCH
;yes, continue |
|
1620 |
;Comparison
failure, |
||
1630 |
;Return to
main loop |
||
066B A4D4 |
1640 |
LDY FRO |
|
066D 18 |
1650 |
CLC
;Used in place |
|
066E 90BD |
1660 |
BCC NOTFOUND
;of JMP (relocatable) |
|
1670 |
; |
||
1680 |
;Match! |
||
1690 |
;Return address
in FRO to BASIC |
||
1700 |
FOUND2 |
||
0670 18 |
1710 |
CLC |
|
0671 A5D4 |
1720 |
LDA FRO |
|
0673 65CB |
1730 |
ADC SADRL |
|
0675 85D4 |
1740 |
STA FRO |
|
0677 A5CC |
1750 |
LDA SADRH |
|
0679 6900 |
1760 |
ADC #0 |
|
067B 85D5 |
1770 |
STA FRO+1 |
|
067D 60 |
1780 |
RTS |
|
1790 |
|||
067E |
1800 |
.END |
=00CB SADRL =00CC SADRH =00CD SLENL =00CE SLENH
=00CF FNDL =00D0 FNDH =00Dl FNDLEN =00D2 FIRSTCHAR
=OOD3 SINDEX =00D4 FRO =00D6 FINDEX =00D7 TADRL
=00D8 TADRH =00D9 ENDLOOP 0600 FIND 0639 SHORT
0621 NXTSRCH 0623 SEARCH2 0627 SEARCHLOOP 0644 FOUND1
062D NOTFOUND 063D EXIT 0654 CONTSRCH 0670 FOUND2
0665 SKIPINC
Program 8-3. 64 Search BASIC Loader.
799 X=PEEK(55):POKE55,X-1:REM PROTECT ML
800 FOR ADRES=40800TO40913:READ DATTA:
POKE ADRES,DATTA:NEXT ADRES
900 PRINT"SYS40800 TO ACTIVATE"
4096 DATA 162, 0, 173, 1, 8, 133
4102 DATA 165, 173, 2, 8, 133, 166
4108 DATA 160, 0, 177, 165, 208, 6
4114 DATA 200, 177, 165, 208, 1, 96
4120 DATA 160, 0, 177, 165, 141, 167
4126 DATA 0, 200, 177, 165, 141, 168
4132 DATA 0, 200, 177, 165, 133, 57
4138 DATA 200, 177, 165, 133, 58, 165
4144 DATA 165, 24, 105, 4, 133, 165
4150 DATA 165, 166, 105, 0, 133, 166
4156 DATA 160, 0, 177, 165, 240, 28
4162 DATA 205, 6, 8, 240, 4, 200
4168 DATA 76, 158, 159, 162, 0, 232
4174 DATA 200, 189, 6, 8, 240, 7
4180 DATA 209, 165, 240, 245, 76, 158
4186 DATA 159, 32, 201, 159, 165, 167
4192 DATA 133, 165, 165, 168, 133, 166
4198 DATA 76, 108, 159, 32, 201, 189
4204 DATA 169, 32, 32, 210, 255, 96
READY.
Program 8-4. Apple Version.
700 FOR AD=768TO900: READ DA:POKE A
D,DA:NEXT AD
768 DATA169,76,141,245,3,169
774 DATA16,141,246,3,169,3
780 DATA141,247,3,96,162,0
786 DATA173,1,8,133,1,173
792 DATA2,8,133,2,160,0
798 DATA177,1,208,6,200,177
804 DATA1,208,1,96,160,0
810 DATA177,1,133,3,200,177
816 DATA1,133,4,200,177,1
822 DATA133,117,200,177,1,133
828 DATA118,165,1,24,105,4
834 DATA133,1,165,2,105,0
840 DATA133,2,160,0,177,1
846 DATA240,28,205,6,8,240
852 DATA4,200,76,76,3,162
858 DATA0,232,200,189,6,8
864 DATA240,7,209,1,240,245
870 DATA76,76,3,'76,119,3
876 DATA165,3,133,1,165,4
882 DATA133,2,76,28,3,169
888 DATA163,32,237,253,32,32
894 DATA237,169,160,32,237,253
900 DATA76,108,3
Program 8-5. VIC-20 Search BASIC Loader.
800 FOR ADRES=828TO941:READ DATTA:POKE ADR
ES,DATTA:NEXT ADRES
810 PRINT"SYS 828 TO ACTIVATE"
828 DATA 162, 0, 173, 1, 16, 133
834 DATA 187, 173, 2, 16, 133, 188
840 DATA 160, 0, 177, 187, 208, 6
846 DATA 200, 177, 187, 208, 1, 96
852 DATA 160, 0, 177, 187, 141, 190
858 DATA 0, 200, 177, 187, 141, 191
864 DATA 0, 200, 177, 187, 133, 57
870 DATA 200, 177, 187, 133, 58, 165
876 DATA 187, 24, 105, 4, 133, 187
882 DATA 165, 188, 105, 0, 133, 188
888 DATA 160, 0, 177, 187, 240, 28
894 DATA 205, 6, 16, 240, 4, 200
900 DATA 76, 122, 3, 162, 0, 232
906 DATA 200, 189, 6, 16, 240, 7
912 DATA 209, 187, 240, 245, 76, 122
918 DATA 3, 32, 165, 3, 165, 190
924 DATA 133, 187, 165, 191, 133, 188
930 DATA 76, 72, 3, 32, 194, 221
936 DATA 169, 32, 32, 210, 255, 96
Return to Table of Contents | Previous Chapter | Next Chapter