;Magic Goto 2.0, November 2014 ;by Ivan X, ivan@ivanx.com, http://appleii.ivanx.com ;Magic GOTO was introduced at KansasFest 2012, http://www.kansasfest.org/ ;License terms: WTFPL (http://sam.zoy.org/wtfpl/) with the following ; exception: somewhere in your program or documentation, include the ; phrase "Magic GOSUB by Ivan Drucker" or equivalent language, e.g.: ; 63999 REM includes Magic GOSUB by Ivan Drucker ; permits GOSUB "LABEL" to a matching REM statement of form ; REM ^LABEL, REM SUB^LABEL, or REM "LABEL" ; exploits the fact that in Applesoft, GOSUB "FOO" performs a GOSUB 0 ; fully relocatable; can run from any address ; for the purposes of this source, "scan" means the initial review ; of all lines to build the label table, and "search" means the ; looking through the label table to execute a GOSUB or GOTO. ;NOTE: INIT (GOSUB $ or CALL to routine start) will set LOMEM and clear variables ;NOTE: if no init, first GOSUB will init before continuing ;NOTE: CLEAR command will cause re-init on next GOSUB ;NOTE: LOMEM after init requires reinit ;NOTE: target labels before MG call not recommended ;NOTE: GOTO requires GOSUB @ beforehand ;NOTE: GOTO without GOSUB @ will be unpredictable (will GOTO the ; target label of an active GOSUB, or yield ?UNDEF'D STATEMENT ERROR) ;NOTE: don't set ONERR GOTO until MG is initialized ;NOTE: GOSUB @@ will cancel GOSUB @ ;NOTE: GOSUB @ will replace any active GOSUB @ ;NOTE: ONERR GOTO error will clear GOSUB @ ;version history ;11/2014: 2.0; called MAGIC GOTO; supports GOTO, ONERR GOTO, search direction and start position ;8/2012: 1.0; called MAGIC GOSUB; supports GOSUB "label" and GOSUB @"label" (GOTO) ;7/2012: beta TXTTAB : $67 ;67/68 ; start of Applesoft program CURLIN : $75 ;75/76 ; line to start searching from (next line after CALL) PRGEND : $AF ;AF/B0 ; end of Applesoft program TXTPTR : $B8 ;B8/B9 ; current line position ERRFLG : $D8 ; ONERR GOTO is active when bit 7 is set ERRPOS : $DC ;DC/DD ; used to detect if arriving via ONERR GOTO "LABEL" TXTPSV : $F4 ;F4/F5 ; address of ONERR GOTO target CURLSV : $F6 ;F6/F7 ; line number of ONERR GOTO REMSTK : $F8 ; stack level before line execution (PEEK(248)) REM : $D9DC ; skip rest of line (handle as REM statement) REMN : $D9A6 ; load Y to with bytes to end of line (pre-ADDON) DATA : $D995 ; skip to next statement (colon or EOL) ADDON : $D998 ; add Y to TXTPTR CLEARC : $D66C ; clear all variables/arrays/strings/reset stack UNDERR : $D97C ; ?UNDEF'D STATEMENT ERROR routine SAVE_PC : $FF54 ; puts stack pos in X, TXS/PLA/PLA can get PC VARTAB : $69 ; 69/6A start of variable space (LOMEM) ARYTAB : $6B ; 6B/6C start of array storage STREND : $6D ; 6D/6E end of array storage FRETOP : $6F ; 6F/70 lower boundary of string storage MEMSIZ : $73 ; 73/74 upper boundary of string storage (HIMEM) tablePtr : $40 ;40/41 ; current address of search table entry startAddr : $42 ;42/43 ; address of line to start searching from targetPtr : $CE ;CE/CF ; start byte of GOTO/GOSUB label labelPtr : $EC ;EC/ED ; address of line containing label (or potential label during scan) origLineNum : $EE ;EE/EF ; line number of GOTO/GOSUB line storePtr : $06 ;06/07 temp1 : $D7 direction : $E3 ; clear bit 6 = search forward, set bit 6 = search backwards stack : $100 ; free: EB, FA-FE [not available if using Slammer+NuInput], 08-09 setV : $B1 EOL : $00 SPC : $20 CARET : $5E COLON : $3A S_CHAR : $53 ATSIGN : $40 BANG : $21 QUOTE : $22 POUND : $23 DOLLAR : $24 PERCENT : $25 ENDTOKEN : $80 FORTOKEN : $81 CALLTOKEN : $8C REMTOKEN : $B2 IFTOKEN : $AD ONTOKEN : $B4 GOTOTOKEN : $AB GOSUBTOKEN : $B0 ;store: ;old LOMEM ;00-05 :contains #0-#5 as signature indicating init ;06 :goto prep indicator (0=normal/gosub, 1=goto prepped, 80=onerr goto) ;07-0D :GOSUB info copied from stack during GOSUB @ ;0E-0F :00 00 to indicate start of label table ;... :table of addresses of lines with REM "LABEL" ;.. :00 00 to indicate end of label table ;.. :address of store start ;LOMEM * = 6000 start: ;===== PRE-INIT ===== ; checks to see if we need init; if not, go to SEARCH ; check if we're being called during RUN rather than GOSUB ; CLD ;default in Applesoft setupStorePtr: DEC VARTAB+1 ;set storePtr to sig/store/table start address (below LOMEM) LDY #FE LDA (VARTAB),Y STA storePtr INY ;#FF LDA (VARTAB),Y STA storePtr+1 INC VARTAB+1 INY ;#00 STY temp1 ;set checkCharLoop flag to not yet processed STY direction ;set direction forward by default STY origLineNum STY origLineNum+1 defaultToTop: LDA TXTTAB ; default address of first line (TXTTAB) for everything STA startAddr STA labelPtr LDA TXTTAB+1 STA startAddr+1 STA labelPtr+1 checkInitSignature: LDY #5 ;see if we are already inited checkInitSigLoop: TYA CMP (storePtr),Y ;if init signature byte doesn't match, init BNE init DEY BPL checkInitSigLoop ;continue loop checkIfRUN: ;signature exists LDX $01F7 CPX #22 ;is it normal stack condition after RUN BNE BNE_search ;if no, already inited, go search; if yes, init resetLomem: LDA storePtr ;reset LOMEM to store address STA VARTAB ;before continuing to INIT LDA storePtr+1 ;to prevent extending LOMEM on every run STA VARTAB+1 ;===== INIT ===== ; creation of empty label table beneath LOMEM on first call or first GOSUB init: initStorePtr: LDA VARTAB ;save current LOMEM in storePtr STA storePtr LDA VARTAB+1 STA storePtr+1 ; set up store area before LOMEM clearStore: LDA #0 ;clear 20 bytes after LOMEM (3 for signature, 4 for status, 7 for store, 2 for table start, 2 for table end, 2 for storePtr) TAY ; and extend LOMEM each time through LDX #~20 clearStore1: STA (VARTAB),Y INC VARTAB BNE clearStore2 INC VARTAB+1 clearStore2: DEX BNE clearStore1 ;if not yet done clearing, loop setInitSignature: LDY #5 ;set bytes at start of store to 00-05 setInitSigLoop: TYA ;so we know we have inited STA (storePtr),Y DEY BPL setInitSigLoop saveStoreStart: DEC VARTAB+1 ;Y is #FF LDA storePtr+1 ;put address of store start STA (VARTAB),Y ;immediately below LOMEM DEY LDA storePtr STA (VARTAB),Y INC VARTAB+1 ;===== SCAN ===== ;scan all lines for labels and build table from them scanLoop: lookForREM: LDY #4 ; check for REM statement LDA (labelPtr),Y CMP # startAddr, try to match compareLoBytes: DEY ;hi bytes are equal, so compare lo bytes BMI matchLine ;if lo bytes are equal, startAddr = tablePtr, try to match BEQ tableLoop2 ;BRA -- hi bytes are equal, compare lo bytes setNextLine: INC tablePtr BNE setNextLine2 INC tablePtr+1 setNextLine2: DEX BEQ setNextLine BMI tableLoop ;BRA -- tablePtr incremented, match next line evalBackward: BCC matchLine ;if backward, and tableAddr < startAddr, try to match BEQ compareLoBytes ;hi bytes are equal, compare lo bytes setPrevLine: LDA tablePtr ;backward, and tableAddr > startAddr, so skip this line BNE setPrevLine2 DEC tablePtr+1 setPrevLine2: DEC tablePtr DEX BEQ setPrevLine BMI tableLoop ;BRA -- tablePtr decremented, match next line ;===== MATCH: COMPARE LABEL ===== matchLine: LDY #1 ;put contents of tablePtr into labelPtr LDA (tablePtr),Y ;so we can look at line's label STA labelPtr+1 DEY LDA (tablePtr),Y STA labelPtr compareLabel: DEY compareLabelLoop: INY ; Y starts at 0 LDA (targetPtr),Y ; check GOSUB/GOTO target character BEQ checkEndOfLabel ; end of line? if yes, check for end of definition CMP #