LLX > Neil Parker > Apple II > Addons for Applesoft
GOTOGOSUBRESTORE to Arbitrary (Computed)
Line NumberON ... RESTORE, similar to
ON ... GOTO/GOSUBPEEKPOKEHere are a few machine-language snippets that add
useful new statements and functions to Applesoft. Most of them are one of
two varieties: new statements, added through Applesoft's ampersand
(&) vector, and new functions, added through the lesser-known
and often-neglected USR vector.
Though the assembly listings below all start at memory location $300,
they are all fully relocatable, and may be positioned anywhere in memory
without change. Only the address to be POKEed into the
ampersand or USR vector would change.
Additionally, the ampersand routines are chainable - if you want to use
more than one of them at a time, just position each one so that its first
byte overwrites the RTS at the end of the previous one.
Unfortunately, the USR functions are not as easily chainable.
USR wasn't designed to allow more than one user-defined
function at a time (not that it can't be done, but doing it requires
jumping through some additional hoops).
Actually setting up the ampersand or USR vector isn't
illustrated below. If you load an ampersand routine at $300, connect it
up like this:
POKE 1013,76: POKE 1014,0: POKE 1015,3
Setting up a USR routine is similar:
POKE 10,76: POKE 11,0: POKE 12,3
The 0 and the 3 will change depending on where you load the routine,
but the 76 ($4C, the 6502 JMP instruction) is always
constant.
(If there's any doubt about which setup to use, look at the syntax example below the code.)
Some of the ampersand routines below end with two consecutive
RTS instructions. This looks a bit strange, but is
intentional: The first one is the normal exit from the routine, and the
second one gets branched to if the token immediately following the
ampersand isn't recognized. This is to facilitate the chaining
technique described above: Overlay the second RTS
(not the first) with the start of the next routine. If you're not
chaining routines together, the second RTS can be eliminated
by adjusting the BNE instruction at the beginning to point to
the first RTS.
License: The code below may be used by anyone at any time for any purpose with no restrictions whatsoever. Though I would appreciate an acknowledgement if you find any of these useful, it's not required. There is, of course, no warranty - I cannot claim that any of them is bug-free, or that you will find any of them useful, nor can I provide any form of formal support or assume any legal liability.
Applesoft's INPUT statement really isn't designed for human
beings to interact with - its parsing rules are somewhat complex, and if
you want to respond to an INPUT with a string that contains
both commas and quotation marks in it, you're out of luck: it's
impossible.
The code below adds a statement similar to the LINE INPUT
found in many other BASICs. You can read into a string variable almost any
ASCII characters, up to 255 characters - the only ASCII characters that
can't be read as data are the standard Apple input-editing keys (Return,
Backspace, Forward Arrow, Escape, and Delete - CHR$'s 13, 8,
21, 27, and 127).
1 INPUTTKN = $84 ;Asoft INPUT token
2 PROMPT = $33 ;Prompt char for GETLN
3 FORPNT = $85 ;Var ptr for GETSPT
4 IN = $200 ;Buffer for GETLN
5 CHRGET = $B1 ;Get next program token
6 GDBUFS = $D539 ;Mask off hi bits of input
7 GETSPT = $DA7B ;Assign FAC to str var
8 STRPRT = $DB3D ;Print string in FAC
9 CHKSTR = $DD6C ;TYPE MISMATCH if not string
10 STRTXT = $DE81 ;Parse string from prog text
11 SYNCHR = $DEC0 ;SYNTAX err if next token<>A
12 PTRGET = $DFE3 ;Parse var name, find in mem
13 ERRDIR = $E306 ;ILLEGAL DIRECT if not running
14 STRSPA = $E3DD ;Get space for new str
15 PUTNEW = $E42A ;Make temp str descript
16 MOVSTR = $E5E2 ;Move data to str space
17 GETLN = $FD6A ;Read line of input
18 ORG $300
0300: C9 84 19 CMP #INPUTTKN ;Is it INPUT?
0302: D0 3E 20 BNE OUT ;Skip if not
0304: 20 06 E3 21 JSR ERRDIR ;Make sure prog is running
0307: 20 B1 00 22 JSR CHRGET ;Get next token
030A: C9 22 23 CMP #$22 ;Quote?
030C: D0 0B 24 BNE GETVAR ;Don't prompt if not
030E: 20 81 DE 25 JSR STRTXT ;Parse str const
0311: 20 3D DB 26 JSR STRPRT ;Print it
0314: A9 3B 27 LDA #';' ;Check for ';'
0316: 20 C0 DE 28 JSR SYNCHR
0319: 20 E3 DF 29 GETVAR JSR PTRGET ;Parse var name
031C: 85 85 30 STA FORPNT ;Save its address
031E: 84 86 31 STY FORPNT+1
0320: 20 6C DD 32 JSR CHKSTR ;Error if not string
0323: A9 80 33 LDA #$80 ;No prompt
0325: 85 33 34 STA PROMPT
0327: 20 6A FD 35 JSR GETLN ;Get input
032A: 8A 36 TXA ;Save its length
032B: 48 37 PHA
032C: 20 39 D5 38 JSR GDBUFS ;Chop off hi bits
032F: 68 39 PLA ;Get saved len
0330: 48 40 PHA
0331: 20 DD E3 41 JSR STRSPA ;Get space for str
0334: A0 02 42 LDY #>IN
0336: A2 00 43 LDX #<IN
0338: 68 44 PLA
0339: 20 E2 E5 45 JSR MOVSTR ;Move input to string space
033C: 20 2A E4 46 JSR PUTNEW ;Make new temp descriptor
033F: 4C 7B DA 47 JMP GETSPT ;Assign descriptor to var
0342: 60 48 OUT RTS
--End assembly, 67 bytes, Errors: 0
The syntax is:
& INPUT ["prompt string";] string-variable
The prompt is optional; if present, it must be a literal quoted string
followed by a semicolon. If it's not present, no prompt at all
is printed - if you want a question mark prompt like the one Applesoft's
INPUT provides, say something like
& INPUT "?";A$.
You only get one variable per & INPUT statement. If you want
to input two or more variables, use two or more & INPUT
statements.
Here's another, rather different way to do the same thing:
1 PROMPT = $33 ;Prompt char for GETLN
2 IN = $200 ;Buffer for GETLN
3 GDBUFS = $D539 ;Mask off hi bits of input
4 STRPRT = $DB3D ;Print string in FAC
5 CHKSTR = $DD6C ;TYPE MISMATCH if not string
6 ERRDIR = $E306 ;ILLEGAL DIRECT if not running
7 STRSPA = $E3DD ;Get space for new str
8 PUTNEW = $E42A ;Make temp str descript
9 MOVSTR = $E5E2 ;Move data to str space
10 GETLN = $FD6A ;Read line of input
11 ORG $300
0300: 20 06 E3 12 JSR ERRDIR ;Err if prog not running
0303: 20 6C DD 13 JSR CHKSTR
0306: 20 3D DB 14 JSR STRPRT ;Print arg
0309: A9 80 15 LDA #$80 ;No prompt
030B: 85 33 16 STA PROMPT
030D: 20 6A FD 17 JSR GETLN ;Get input
0310: 8A 18 TXA ;Save its length
0311: 48 19 PHA
0312: 20 39 D5 20 JSR GDBUFS ;Chop off hi bits
0315: 68 21 PLA ;Get saved len
0316: 48 22 PHA
0317: 20 DD E3 23 JSR STRSPA ;Get space for str
031A: A0 02 24 LDY #>IN
031C: A2 00 25 LDX #<IN
031E: 68 26 PLA
031F: 20 E2 E5 27 JSR MOVSTR ;Move input to string space
0322: 68 28 PLA ;Must pop 1 return addr
0323: 68 29 PLA ; before returning a string
0324: 4C 2A E4 30 JMP PUTNEW ;Return new temp descriptor
--End assembly, 39 bytes, Errors: 0
The syntax is:
USR (string-expression)
This is a function that returns a string, and can be used anywhere in an expression where a string-valued function would be legal. The argument is printed as a prompt, and in this case you can use any string expression at all, not just a literal quoted string. For example:
100 P$ = "Yes, Master?" 110 A$ = USR (P$ + " ") 120 PRINT "You typed "; A$
If you want no prompt at all, pass an empty string, for example:
200 A$ = USR ("")
GOTOOne feature that old Integer BASIC programmers often missed in Applesoft
is the ability to GOTO any numeric expression, not just a
literal line number. Applesoft does have ON ... GOTO,
but it's just not the same.
Adding an Integer-like computed GOTO to Applesoft is
particularly easy:
1 GOTOTKN = $AB ;Asoft GOTO token
2 CHRGET = $B1 ;Get next program token
3 GOTO = $D93E ;Parse & run GOTO stmt
4 FRMNUM = $DD67 ;Eval numeric expr
5 GETADR = $E752 ;Convert to 2-byte int
6 ORG $300
0300: C9 AB 7 CMP #GOTOTKN ;Is it GOTO?
0302: D0 0C 8 BNE OUT ;Skip if not
0304: 20 B1 00 9 JSR CHRGET ;Get next token
0307: 20 67 DD 10 JSR FRMNUM ;Eval numeric expr
030A: 20 52 E7 11 JSR GETADR ;Convert to int
030D: 4C 41 D9 12 JMP GOTO+3 ;Find line in mem & goto it
0310: 60 13 OUT RTS
--End assembly, 17 bytes, Errors: 0
Syntax:
& GOTO numeric-expression
Of course the expression must evaluate to an actual line number in your
program, or you'll get ?UNDEF'D STATEMENT ERROR.
The use of the GETADR routine (the same routine used to
get the memory address for PEEK, POKE, and
CALL) has an odd side effect: negative line numbers
are accepted. The routine will make it positive by adding 65536 to it.
GOSUBInteger BASIC also had computed GOSUBs. Here it is for
Applesoft:
1 GOSUBTKN = $B0 ;Asoft GOSUB token
2 CURLIN = $75 ;Current line no.
3 TXTPTR = $B8 ;Current token addr
4 CHRGET = $B1 ;Get next program token
5 GETSTK = $D3D6 ;Check stack space
6 NEWSTT = $D7D2 ;Execute statements
7 GOTO = $D93E ;Go to new line no.
8 FRMNUM = $DD67 ;Eval numeric expression
9 GETADR = $E752 ;Convert number to 2-byte int
10 ORG $300
0300: C9 B0 11 CMP #GOSUBTKN ;Is it GOSUB?
0302: D0 23 12 BNE OUT ;Skip if not
0304: A9 03 13 LDA #3 ;Make sure there's enough stack
0306: 20 D6 D3 14 JSR GETSTK
0309: A5 B9 15 LDA TXTPTR+1 ;Push marker for RETURN
030B: 48 16 PHA
030C: A5 B8 17 LDA TXTPTR
030E: 48 18 PHA
030F: A5 76 19 LDA CURLIN+1
0311: 48 20 PHA
0312: A5 75 21 LDA CURLIN
0314: 48 22 PHA
0315: A9 B0 23 LDA #GOSUBTKN
0317: 48 24 PHA
0318: 20 B1 00 25 JSR CHRGET ;Get next token
031B: 20 67 DD 26 JSR FRMNUM ;Parse numeric expr
031E: 20 52 E7 27 JSR GETADR ;Convert it to int
0321: 20 41 D9 28 JSR GOTO+3 ;Point at chosen statement
0324: 4C D2 D7 29 JMP NEWSTT ;Start running it
0327: 60 30 OUT RTS
--End assembly, 40 bytes, Errors: 0
Syntax:
& GOSUB numeric-expression
Again, the expression must evaluate to an actual line number, or you'll
get ?UNDEF'D STATEMENT ERROR. Negative line numbers are
accepted just as with computed GOTO.
RESTORE to Arbitrary (Computed) Line
NumberIt's sometimes handy to be able to RESTORE to a
DATA statement other than the first one in the program, and
many other BASICs allow it. This adds it to Applesoft:
1 RESTORETKN = $AE ;Asoft RESTORE token
2 DATPTR = $7D ;DATA stmt pointer
3 LOWTR = $9B ;FNDLIN puts link ptr here
4 CHRGET = $B1 ;Get next program token
5 FNDLIN = $D61A ;Find line in memory
6 FRMNUM = $DD67 ;Evaluate a numeric expression
7 GETADR = $E752 ;Convert number to 2-byte int
8 ORG $300
0300: C9 AE 9 CMP #RESTORETKN ;Is it RESTORE?
0302: D0 19 10 BNE OUT ;Skip if not
0304: 20 B1 00 11 JSR CHRGET ;Get next token
0307: 20 67 DD 12 JSR FRMNUM ;Eval expression
030A: 20 52 E7 13 JSR GETADR ;Convert to int
030D: 20 1A D6 14 JSR FNDLIN ;Find chosen line no.
0310: A4 9C 15 LDY LOWTR+1 ;Point DATPTR at byte before it
0312: A6 9B 16 LDX LOWTR
0314: D0 01 17 BNE DX
0316: 88 18 DEY
0317: CA 19 DX DEX
0318: 84 7E 20 STY DATPTR+1
031A: 86 7D 21 STX DATPTR
031C: 60 22 RTS
031D: 60 23 OUT RTS
--End assembly, 30 bytes, Errors: 0
Syntax:
& RESTORE numeric-expression
After executing & RESTORE, the next READ
will start searching for DATA statements at the indicated line
number.
The line number can be any numeric expression. It does not need
to evaluate to a line number that actually exists in your program; if there
is no such line number, READ will start searching at the next
higher line number that does actually exist.
Negative line number are accepted just as with
& GOTO and
& GOSUB.
ON ... RESTOREFor those who prefer their computed RESTORE to work in a
more classically Applesoft-like manner, here's an ON ... RESTORE
statement that works like ON ... GOTO and ON ...
GOSUB:
1 RESTORETKN = $AE ;Asoft RESTORE token
2 ONTKN = $B4 ;Asoft ON token
3 DATPTR = $7D ;DATA stmt pointer
4 LOWTR = $9B ;FNDLIN puts link ptr here
5 FACLO = $A1 ;GETBYT puts result here
6 CHRGET = $B1 ;Get next program token
7 FNDLIN = $D61A ;Find line in memory
8 DATA = $D995 ;DATA stmt handler - skip to end of stmt
9 LINGET = $DA0C ;Parse line number
10 SNERR = $DEC9 ;Fail with SYNTAX err
11 GETBYT = $E6F8 ;Evaluate expr in range 0..255
12 ORG $300
0300: C9 B4 13 CMP #ONTKN ;Is it ON?
0302: D0 34 14 BNE OUT ;Skip if not
0304: 20 B1 00 15 JSR CHRGET ;Get next token
0307: 20 F8 E6 16 JSR GETBYT ;Eval expr, ILLEGAL QUANTITY if not in 0..255
030A: C9 AE 17 CMP #RESTORETKN ;Is next token RESTORE?
030C: F0 03 18 BEQ COUNT ;Continue if so
030E: 4C C9 DE 19 JMP SNERR ;Else SYNTAX err
0311: C6 A1 20 COUNT DEC FACLO ;Skipped enough line nums yet?
0313: D0 18 21 BNE NXTNUM ;If not, go skip another
0315: 20 B1 00 22 JSR CHRGET ;Else advance to 1st digit
0318: 20 0C DA 23 JSR LINGET ;Parse it
031B: 20 1A D6 24 JSR FNDLIN ;Find it in memory
031E: A4 9C 25 LDY LOWTR+1 ;Point DATPTR at byte before it
0320: A6 9B 26 LDX LOWTR
0322: D0 01 27 BNE DX
0324: 88 28 DEY
0325: CA 29 DX DEX
0326: 84 7E 30 STY DATPTR+1
0328: 86 7D 31 STX DATPTR
032A: 4C 95 D9 32 JMP DATA ;Skip over any remaining line nums & exit
032D: 20 B1 00 33 NXTNUM JSR CHRGET ;Skipping - advance to 1st digit
0330: 20 0C DA 34 JSR LINGET ;Parse line num (& ignore it)
0333: C9 2C 35 CMP #',' ;Followed by comma?
0335: F0 DA 36 BEQ COUNT ;If so, check if next line num is right
0337: 60 37 RTS ;Else we're done.
0338: 60 38 OUT RTS
--End assembly, 57 bytes, Errors: 0
Syntax:
& ON numeric-expression RESTORE linenum[,linenum[,...]]
As with Applesoft's ON ... GOTO/GOSUB, the numeric expression
is any expression that evaluates to a number in the range 0 to 255. The
linenums are a list of literal line numbers (no expressions allowed)
separated by commas, and the numeric expression selects which line number
to RESTORE to: if the expression is 1, the first line number
is used, and if the expression is 2, the second line number is used, and so
on. If the expression is 0 or greater than the number of line numbers, no
RESTORE is done, and no error occurs.
If the expression is less than zero or greater than 255, ?ILLEGAL
QUANTITY ERROR occurs.
The line number chosen need not actually exist in the program. If it
doesn't, READ will start searching at the next higher line
that does exist.
PEEKSometimes it's convenient to deal with data PEEKed from
memory in two-byte quantities. Here's a routine to help with that:
1 LINNUM = $50 ;Result from GETADR
2 CHKNUM = $DD6A ;Make sure expr is number
3 GIVAYF = $E2F2 ;Float signed int in A,Y
4 GETADR = $E752 ;Convert number to int
5 ORG $300
0300: 20 6A DD 6 JSR CHKNUM ;TYPE MISMATCH if not number
0303: A5 51 7 LDA LINNUM+1 ;Preserve LINNUM
0305: 48 8 PHA
0306: A5 50 9 LDA LINNUM
0308: 48 10 PHA
0309: 20 52 E7 11 JSR GETADR ;Convert num to int
030C: A0 01 12 LDY #1 ;Get value from mem into X,Y
030E: B1 50 13 LDA (LINNUM),Y
0310: AA 14 TAX
0311: 88 15 DEY
0312: B1 50 16 LDA (LINNUM),Y
0314: A8 17 TAY
0315: 68 18 PLA ;Restore saved LINNUM
0316: 85 50 19 STA LINNUM
0318: 68 20 PLA
0319: 85 51 21 STA LINNUM+1
031B: 8A 22 TXA
031C: 4C F2 E2 23 JMP GIVAYF ;Float result
--End assembly, 31 bytes, Errors: 0
Syntax:
USR (numeric-expression)
The argument must evaluate to a number from 0 to 65535 (or equivalently,
-65536 to -1), which is interpreted as a memory address. W = USR
(A) is roughly equivalent to W = PEEK (A) + 256 * PEEK
(A + 1), except that runs faster and with fewer tokens, and it
returns its result as a signed number (in the range -32768 ...
32767). If you need a positive result, you can get it like this:
W = USR (A): IF W < 0 THEN W = W + 65536
POKEHere's the inverse of Two-byte PEEK -
a statement that POKEs a two-byte number into memory:
1 POKETKN = $B9 ;Asoft POKE token
2 LINNUM = $50 ;Result from GETADR
3 FORPNT = $85 ;Temp pointer
4 CHRGET = $B1 ;Get next program token
5 FRMNUM = $DD67 ;Evaluate a numeric expression
6 CHKCOM = $DEBE ;SYNTAX err if not comma
7 GETADR = $E752 ;Convert num to 2-byte int
8 ORG $300
0300: C9 B9 9 CMP #POKETKN ;Is it POKE?
0302: D0 2A 10 BNE OUT ;Skip if not
0304: 20 B1 00 11 JSR CHRGET ;Get next token
0307: 20 67 DD 12 JSR FRMNUM ;Eval expression
030A: 20 52 E7 13 JSR GETADR ;Convert to int
030D: A5 51 14 LDA LINNUM+1 ;Save it
030F: 48 15 PHA
0310: A5 50 16 LDA LINNUM
0312: 48 17 PHA
0313: 20 BE DE 18 JSR CHKCOM ;Check for comma
0316: 20 67 DD 19 JSR FRMNUM ;Eval expression
0319: 20 52 E7 20 JSR GETADR ;Convert to int
031C: 68 21 PLA ;Get saved number
031D: 85 85 22 STA FORPNT
031F: 68 23 PLA
0320: 85 86 24 STA FORPNT+1
0322: A0 00 25 LDY #0 ;Store 2 bytes in memory
0324: A5 50 26 LDA LINNUM
0326: 91 85 27 STA (FORPNT),Y
0328: C8 28 INY
0329: A5 51 29 LDA LINNUM+1
032B: 91 85 30 STA (FORPNT),Y
032D: 60 31 RTS
032E: 60 32 OUT RTS
--End assembly, 47 bytes, Errors: 0
Syntax:
& POKE numeric-expression,numeric-expression
Each argument must evaluate to a number from 0 to 65535 (or equivalently,
-65536 to -1). The first argument is a memory address, and the second is a
value to be POKEed into that address. & POKE
A,W is roughly equivalent to WH = INT (W / 256):WL = W - WH *
256: POKE A,WL: POKE A + 1,WH, except that it runs faster and with
fewer tokens, and doesn't use any temporary variables, and it handles
negative values properly.
Here's a routine to delete an array. This can be handy for saving memory, or if you need to re-dimension an array.
1 DELTKN = $85 ;DEL token
2 A1L = $3C ;MOVE source start
3 A1H = $3D
4 A2L = $3E ;MOVE source end
5 A2H = $3F
6 A4L = $42 ;MOVE dest start
7 A4H = $43
8 STREND = $6D ;Ptr to end of vars
9 LOWTR = $9B ;GETARYPT puts address here
10 CHRGET = $B1 ;Get next token
11 GETARYPT = $F7D9 ;Find array in memory
12 MOVE = $FE2C ;Block move
13 ORG $300
0300: C9 85 14 CMP #DELTKN ;Is it DEL?
0302: D0 44 15 BNE DONE ;Skip if not
0304: 20 B1 00 16 JSR CHRGET ;Advance to next token
0307: 20 D9 F7 17 JSR GETARYPT ;Parse array name & find
030A: A5 9B 18 LDA LOWTR ;Array address -> MOVE dest
030C: 85 42 19 STA A4L
030E: A5 9C 20 LDA LOWTR+1
0310: 85 43 21 STA A4H
0312: A0 02 22 LDY #2 ;Offset to array len
0314: B1 9B 23 LDA (LOWTR),Y ;Next array address -> MOVE src start
0316: 18 24 CLC
0317: 65 9B 25 ADC LOWTR
0319: 85 3C 26 STA A1L
031B: C8 27 INY
031C: B1 9B 28 LDA (LOWTR),Y
031E: 65 9C 29 ADC LOWTR+1
0320: 85 3D 30 STA A1H
0322: A5 3C 31 LDA A1L ;Anything to move?
0324: C5 6D 32 CMP STREND
0326: A5 3D 33 LDA A1H
0328: E5 6E 34 SBC STREND+1
032A: B0 13 35 BCS FIXPTR ;If not, don't bother moving
032C: A5 6E 36 LDA STREND+1 ;End of arrays minus 1 -> MOVE src end
032E: 85 3F 37 STA A2H
0330: A5 6D 38 LDA STREND
0332: 85 3E 39 STA A2L
0334: D0 02 40 BNE DLO
0336: C6 3F 41 DEC A2H
0338: C6 3E 42 DLO DEC A2L
033A: A0 00 43 LDY #0
033C: 20 2C FE 44 JSR MOVE ;Leaves A4H,A4L pointing at new end of vars
033F: A5 42 45 FIXPTR LDA A4L ;Adjust end of vars
0341: 85 6D 46 STA STREND
0343: A5 43 47 LDA A4H
0345: 85 6E 48 STA STREND+1
0347: 60 49 RTS
0348: 60 50 DONE RTS
--End assembly, 73 bytes, Errors: 0
Syntax:
& DEL array-name
The array-name is the name of any previously-DIM'd (or
default-dimensioned) array, without the subscript specification. For
example, after DIM A(100), the statement
& DEL A will delete the array A. (The
non-array variable A, if it exists, will be unaffected.)
Only one array can be deleted per & DEL statement. The
array must already exist; if it doesn't, you'll get ?OUT OF DATA
ERROR.
Any array - real, integer, or string - can be deleted (though in the
case of string arrays, the strings themselves aren't freed...you'll need
to use FRE (0), or under ProDOS,
PRINT CHR$ (4);"FRE" to do that).
Deleting an array makes Applesoft forget entirely about it, and it can subsequently be re-created with different dimensions without error.
A corresponding function for deleting non-array variables is not supplied,
as each deleted variable would recover only seven bytes of memory, and
because deleting a non-array variable would require scanning the variable
table for later DEF FN functions and adjusting them (this
isn't an issue for arrays because there are no arrays of DEF FN
functions).
Heres's a routine that moves Applesoft programs to different memory locations. This is often desirable, for example, to move a large program that uses hi-res graphics out of the way of the hi-res screen buffer.
Many programs do this without machine language help, by starting with a program line something like this:
10 IF PEEK (103) + 256 * PEEK (104) < > 16385 THEN POKE 103,1:
POKE 104,64: POKE 16384,0: PRINT CHR$ (4);"RUN THIS.PROGRAM"
But that ends up loading your program twice, once at the standard address, and once at the new address. If you use this routine, you only have to load your program once.
Due to the length of this routine, only the object code is shown here; the assembly listing is in a separate file.
0300:20 BE DE 20 67 DD 20 52 E7 A5 50 38 E5 67 85 40 0310:A5 51 E5 68 85 41 05 40 F0 22 B0 22 A5 67 85 3C 0320:A5 68 85 3D A5 AF 85 3E A5 B0 85 3F A5 50 85 42 0330:A5 51 85 43 A0 00 20 2C FE 98 F0 1E F0 70 A5 67 0340:85 9B A5 68 85 9C A5 AF 85 96 18 65 40 85 94 A5 0350:B0 85 97 65 41 85 95 20 9A D3 A5 50 85 3C A5 51 0360:85 3D C8 B1 3C F0 17 88 B1 3C 18 65 40 91 3C AA 0370:C8 B1 3C 65 41 91 3C 85 3D 86 3C 98 D0 E5 A5 51 0380:85 68 A5 50 85 67 D0 02 C6 51 C6 50 88 98 91 50 0390:A5 AF 18 65 40 85 AF 85 69 A5 B0 65 41 85 B0 85 03A0:6A A5 B8 18 65 40 85 B8 A5 B9 65 41 85 B9 20 6C 03B0:D6 4C D2 D7
This routine is unusual in that it doesn't use ampersand or
USR. It's used like this:
CALL 768,numeric-expression
where the numeric expression gives the address where the program should be moved to.
The address you pass should be address where you want the first byte of
of the moved program to go. Be careful to choose an address with at least
one free byte of memory before it...various parts of Applesoft need that
byte to be there (it must contain the value 0, but you don't need to
POKE it yourself - the routine takes care of that for you).
Thus you should use the address 16385 to move above hi-res page 1, or
24577 to move above hi-res page 2, or 2049 to move back to the standard
location.
Warnings:
CALL 768,address throws away all your variables and pending
NEXTs and RETURNs. You're expected to use it
exactly twice in your program - once at startup to move to your safe
address, and once just before exiting, to move back to 2049.HIMEM, the
routine will allow it, and then fail disastrously.The version of the Applesoft program relocator that used to be on this page before June 19, 2018 (the 183-byte version) had a bug in it that would damage your program if the new starting address was between the old starting address and the old ending address. Throw it away and use this new version instead.
Source code files are for the Merlin assembler.
LLX > Neil Parker > Apple II > Addons for Applesoft
Original: June 2, 2016
Modified February 26, 2017--added ON ... RESTORE
Modified March 27, 2018--minor rewording; added downloadable files
Modified June 5, 2018--added Applesoft program relocator
Modified June 19, 2018--fixed a bug in the Applesoft program
relocator
Modified June 26, 2018--added array deleter
Modified November 23, 2021--added separate DOS 3.3 .sdk archive