PGOETZ@LOYVAX.BITNET (04/05/89)
For the fellow who asked how Applesoft programs can modify themselves,
here is a reprint of an article I wrote for COMPUTIST a year or two ago.
The disadvantage compared to the "poke into a REM statement" method is that
this method destroys variables.
The idea came to me from some code Jack ? (I can't remember his last
name; how embarassing!) wrote for the Gemini personal robot (a beautiful
piece of hardware which never succeeded in the mass market). It was part
of the ROM intro program, and entered a program and invited the user to
RUN it - much like the first of the 3 programs below.
Generating Applesoft Programs on the Fly
Sometimes you may want to add lines to an Applesoft program while it is
running, or you may want your utility to write an entire Applesoft program.
You can do this by directing the input vector ($38-39) to a short machine
language program which hands BASIC the correct characters one by one, just as
if they had been typed from the keyboard. As a simple example, the following
program writes and executes a canned Applesoft program. It is written for the
S-C Macro Assembler. .AS -/HI THERE/ produces the negative ASCII (high bit
set) for the string HI THERE. .BS x tells the assembler to allocate x bytes at
that point in the object code. .HS produces a "hex string", so .HS 8D places a
$8D in the object code. #KEYINT and /KEYINT represent the low and high bytes,
respectively, of the word KEYINT.
[I inserted a > at the beginning of lines with local labels; some gateways
strip leading .'s.]
*--------------------------------
* GENERATE & RUN APPLESOFT PROG
*--------------------------------
.OR $300
CALLME LDA #KEYINT LOAD NEW INPUT VECTOR
STA $38
LDA /KEYINT
STA $39
JSR $3EA DOS ROUTINE TO SAVE OUR INTERCEPT
* TO BE CALLED AFTER DOS IS THRU
LDA #0
STA BUFPTR
STA LINCNT
JMP $E000 COLDSTART BASIC
BUFPTR .BS 1
LINCNT .BS 1
TEMP .BS 1 TEMPORARY STORAGE FOR X
KEYINT STX TEMP SAVE X
LDX BUFPTR
INC BUFPTR
LDA PROGRM,X
LDX TEMP RESTORE X
CMP #$8D RETURN
BEQ .1
RTS
>.1 INC LINCNT COUNT LINES SO WE KNOW WHEN WE'RE DONE
LDA LINCNT
CMP #3 2 LINES + RUN
BEQ .2
LDA #$8D
RTS
>.2 JSR $FE89 PUT BACK NORMAL VECTOR
JSR $3EA & TELL DOS
LDA #$8D CR
LDX TEMP RESTORE X
RTS
PROGRM .AS -/10?:?"PRESS ANY KEY FOR A COLOR DEMO";:GETA$/
.HS 8D
.AS -@20GR:FORX=0TO31:COLOR=X/2:VLIN0,39ATX:NEXT@
.HS 8D
.AS -/RUN/
.HS 8D
More complicated is making an Applesoft program add lines to itself.
Say you have a curve graphing program which asks the user for an equation such
as Y = X * SIN(X), and you want to enter the equation as a line number. You
can input the equation as a string, add a line number to its left end, pass it
to a machine language routine based on the one above which enters the string as
a direct Applesoft command, and restart your program on the line after the
call. (Note that you can also enter other commands besides adding line numbers
this way; but you can do most other things from within the program anyway.)
Unfortunately, adding new lines overwrites some of the real and integer
variables, and will invalidate the pointers to any strings which are stored
within the program beyond the point where the new line was added. (The string
A$ in 110 A$="HERE I AM" is not duplicated in memory; instead, the string's
pointers point right into the program code.) To avoid problems, Applesoft
erases almost all variables
(including DIM'd array space) whevener you enter, change, or delete a line.
Thus, this technique is unlikely to be useful in the middle of a long program.
To add a line number to a program using the routine below, set a
string equal to the command you want entered, and then CALL 768,string$. The
routine will return control to the next line in your program, with all
variables erased. For instance, 150 A$="20 IF AT = 15 THEN 300" : CALL 768,A$
would add line 20 to your program and restart the program at the next line
after 150.
This program should not work for Integer BASIC or the original RAM
Applesoft for 48K ][s.
*-----------------------------------
* ADD A LINE TO AN APPLESOFT PROGRAM
*-----------------------------------
.OR $300
CURLIN .EQ $75 AND 76: CURRENT LINE #
DSCRPT .EQ 6 STRING DESCRIPTOR
LENGTH .EQ 6 OF STRING
ADDR .EQ 7 AND 8: STRING LOCATION
CALLME JSR $DEBE CHECK FOR A COMMA
JSR $DD7B LOCATE THE STRING
LDY #2
>.1 LDA ($A0),Y GET STRING DESCRIPTOR (LENGTH & LOCATION)
STA DSCRPT,Y
DEY
BPL .1
INC LENGTH ADD 1 TO SIMPLIFY COUNT LATER
LDA #KEYINT LOAD NEW INPUT VECTOR
STA $38
LDA /KEYINT
STA $39
JSR $3EA DOS ROUTINE TO SAVE OUR INTERCEPT TO
* BE CALLED AFTER DOS IS THRU
LDA #0
STA CHRPTR START AT BEGINNING OF STRING
LDA CURLIN SAVE CURRENT LINE #
STA LINE
LDA CURLIN+1
STA LINE+1
JMP $E003 WARMSTART BASIC
CHRPTR .BS 1 POINTER INTO STRING TO CURRENT CHARACTER
LINE .BS 2 LINE # ROUTINE IS CALLED FROM
TEMP .BS 1 TEMPORARY STORAGE FOR Y
KEYINT LDA LENGTH
BEQ .2 BRANCH IF ENTIRE LINE HAS BEEN ENTERED
STY TEMP SAVE Y
LDY CHRPTR
INC CHRPTR
LDA (ADDR),Y GET NEXT CHARACTER FROM STRING
ORA #$80 SET HIBIT TO PRETEND IT'S FROM KEYBOARD
LDY TEMP RESTORE Y
DEC LENGTH DONE?
BNE .1
LDA #$8D CR TO ENTER LINE
>.1 RTS
>.2 JSR $FE89 PUT BACK NORMAL INPUT VECTOR
JSR $3EA & TELL DOS
LDA LINE
STA $50
LDA LINE+1
STA $51
JSR $D61A FIND LINE IN $51,50
* LEAVES $9C,9B POINTING TO THE 1ST BYTE OF THE LINE
LDY #0
LDA ($9B),Y FIND START OF NEXT LINE
STA CURLIN MAKE NEXT LINE CURRENT LINE
CLC
ADC #$FF POINT TXTPTR TO 1 BYTE BELOW START OF NEXT LINE
STA $B8 $B9,B8 POINTS TO BASIC CODE BEING INTERPRETED
INY
LDA ($9B),Y
STA CURLIN+1
ADC #$FF
STA $B9
JMP $D7D2 RUN REST OF PROGRAM
Here is a simple curve-sketching program using this technique:
10 HGR : HCOLOR= 3: HPLOT 140,0 TO 140,191: HPLOT 0,96 TO 279,96: IF PEEK
(768) + PEEK (769) < > 222 THEN PRINT CHR$ (4)"BLOADADDLINE.OBJ
20 HOME : VTAB 21: INPUT "ENTER YOUR FUNCTION -> ";F$:F$ = "40" + F$:
CALL 768,F$
30 F = 0: FOR X = - 25 TO 25
50 Y = Y * 2:X2 = X * 2: IF Y < - 96.4 OR Y > 96.4 THEN FOR A = 0 TO 3:
P = PEEK (49200): NEXT : NEXT : GOTO 80
60 IF NOT F THEN HPLOT X2 + 140.5,96.5 - Y:F = 1: NEXT : GOTO 80
70 HPLOT TO X2 + 140.5,96.5 - Y:F = 1: NEXT
80 INPUT "ANOTHER FUNCTION ? ";F$: IF F$ = "N" THEN END
90 INPUT "ERASE THE GRAPH ? ";F$: IF F$ = "Y" THEN 10
100 GOTO 20
----------
Phil Goetz
PGOETZ@LOYVAX.bitnet
In order of preference: C, UNIX, Assembler, LISP, VMS, Forth, DOS 3.3, BASIC,
ProDOS, cassette tape, Fortran, punched cards, Pascal,
toggle switches, death, COBOL
Disclaimer: Any resemblances to persons real or imaginary is very probably
intentional and malicious. So sue me.