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.