[comp.sys.apple] DEF FN trouble - an answer

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.