|
 |
 |
8
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
BACIC 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 |
| |
|