LLX > Neil Parker > Apple II > Addons for Applesoft
GOTO
GOSUB
RESTORE
to Arbitrary (Computed)
Line NumberON ... RESTORE
, similar to
ON ... GOTO/GOSUB
PEEK
POKE
Here 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 POKE
ed 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 ("")
GOTO
One 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.
GOSUB
Integer BASIC also had computed GOSUB
s. 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 ... RESTORE
For 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.
PEEK
Sometimes it's convenient to deal with data PEEK
ed 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
POKE
Here's the inverse of Two-byte PEEK
-
a statement that POKE
s 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 POKE
ed 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
NEXT
s and RETURN
s. 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