Basic, auch wenn es von GFA kommt, hat gegenĂŒber den klassischen Compiler-Sprachen wie PASCAL oder C einen besonderen gravierenden Nachteil: Variable, Unterprogrammnamen und Labels können an jeder beliebigen Stelle im Programm eingefĂŒhrt werden, ohne vorher deklariert zu werden.
Das mag dem Basic-Freund zunĂ€chst als Vorteil erscheinen. Wer jedoch schon einmal eine schlaflose Nacht auf der Suche nach einem Programmierfehler verbracht hat, um hinterher festzustellen, daĂ bloĂ ein Tippfehler zu einer neuen Variablen mit dem sinnlosen Anfangswert Null gefĂŒhrt hat, wird vielleicht Zweifel bekommen haben.
Ein Pascal-Compiler z. B. hĂ€tte es gar nicht so weit kommen lassen: Bei der Ăbersetzung und dem Linken wird gnadenlos jeder âUndefined Identifierâ aufgedeckt.
Um Abhilfe zu schaffen, wurde das vorliegende Cross-Referenz-Programm entwickelt. Es ermöglicht das identifizieren und Lokalisieren aller vom Programmierer erfundenen Bezeichner in einem im ASCII-Format abgespeicherten Programmtext (.LST-File).
Beim Speichern eines Programms mit âSAVE,Aâ ist zu beachten, daĂ zuvor mit der Anweisung âDEFLIST 0â die Standard-Textdarstellung verĂ€ndert wird.
Hierdurch wird bewirkt, daĂ alle GFA-SchlĂŒsselworte in GroĂschreibung und sĂ€mtliche Labels, Variablen- und Unterprogrammnamen mit kleinen Buchstaben erscheinen.
Damit ist ein einfaches Unterscheidungsmerkmal fĂŒr die im Programm verwendete Such-Procedure gegeben.
Die Benutzung von XREF ist denkbar einfach und vollstĂ€ndig mit Alert-Boxen durchzufĂŒhren. Wie so oft empfiehlt sich die Verwendung einer RAM-Floppy.
Listing
Abbildung 1
Nach Auswahl des zu bearbeitenden Programms mit der File-Select-Box geht es los. Links oben auf dem Bildschirm sieht man die Nummer der gerade untersuchten Programmzeile: man kann die Bearbeitung an dieser Stelle mit einem Druck auf die Escape-Taste vorzeitig abbrechen.
Sollte das ausgewÀhlte Programm im falschen Textformat vor liegen, so wird dies meist nach wenigen Zeilen entdeckt und mit einer Alert-Meldung kommentiert. Der Anwender hat nun die Möglichkeit, das Cross-Referenz-Programm zu verlassen, um das BAS-File in den Interpreter nachladen zu lassen.
Liegt das Programm nur als ASCII-Text vor, muĂ man XREF abbrechen und mit âNEWâ löschen, um âMERGEâ zu verwenden. Nach Abspeichern mit âSAVE,Aâ kann XREF erneut gestartet werden.
Ist dann alles glatt durchgelaufen, kann man die Cross-Referenz-Liste oder den mit Zeilennummern versehenen Programmtext auf Drucker oder Bildschirm ausgeben.
Bei der Anzeige auf dem Bildschirm kann man mit âControl-Sâ die Ausgabe anhalten und mit â Control-Qâ weiterlaufen lassen. Jeweils nach einer Bildschirm-Seite wird automatisch angehalten und auf einen Tastendruck oder Mouse-Klick gewartet.
Bevor man XREF verlĂ€Ăt, hat man die Möglichkeit, die Arbeit auf Diskette zu sichern. Das Cross-Referenz-Listing wird mit der Extension â.CRFâ, das numerierte Programm-Listing mit â.PRNâ, gespeichert.
Mit XREF sollte dem GFA-Basic-Programmierer das Leben hinsichtlich des AufspĂŒrens von Tippfehlern, aber auch bei der Verbesserung der Dokumentation, erleichtert werden.
' ***********************************************************
' * *
' * â XREF Vers 1.2 â *
' * Cross-Reference-Listings fĂŒr GFA-Basic Programme *
' * *
' * 14.03.1987 H. Bauch *
' * *
' ***********************************************************
'
ALERT 0," XREF Version 1.2! | 14.03.1987 by H. Bauch! ",1," Na los ",dunmy%
@init
@bearbeiten
DO
CLS
PRINT
PRINT " ";akt_zeile%;" Zeilen in ";prg.name$;".LST"
ALERT 1,"Programm durchsucht!",1,"Ausgabe|Neul|Quit",antw%
CLS
ON antw% GOSUB ausgabe,bearbeiten,ende
LOOP
'
' _____________________________________________________________
' ------------- Ende des Hauptprogranms -----------------------
' _____________________________________________________________
'
'
PROCEDURE init
DEFLIST 0
LET typ_max%=5 ! 0=Real/1=Integer/2=String/3=Boolean/4=Onterp./5=Label
LET name_max%=200 ! max. Anz. der Namen pro Typ
sp_anz_zeil%=55 ! Spaltenanzahl fĂŒr die Zeilennr.-Ausgabe
DIM name$(typ_max%,1,name_max%) ! Variablen-, Label oder Uprg.-name
DIM zeile$(typ_max%,1,name_max%) ! Zeilennr. in der ein Name steht
DIM zeilen_zaehler%(typ_max%,1,name_max%) ! ZĂ€hler fĂŒr akt. Anz. Zeilennr.
DIM max_nam_anz%(typ_max%,1)
DIM typ$(typ_max%)
typ$(0)="Real-Variablen"
typ$(1)="Integer-Variablen"
typ$(2)="String-Variablen"
typ$(3)="Boolean-Variablen"
typ$(4)="Unterprogramm-Namen"
typ$(5)="Labels"
trenn_zei$="+-*/\' ()=<>,; "+CHRS(34) ! Diese Zeichen trennen Bezeichner.
inv_ein$=CHR$(27)+CHRS(112) ! Bildschirmausgabe invers
inv_aus$=CHR$(27)+CHR$(113) ! - " - normal
RETURN! Init
'
'
PROCEDURE bearbeiten
ARRAYFILL zeilen_zaehler%(),0
ARRAYFILL max_nam_anz%(),0
DEFTEXT 1,16
TEXT 125,30,"Welches Programm soll bearbeitet werden ?"
FILESELECT "\*.LST","",p.name$
IF EXIST(p.name$)
OPEN "I",#1,p.name$
p.name$=MID$(p.name$,1,LEN(p.name$)-4)
p%=INSTR(p.name$,"\",p%)
WHILE p%<>0
po%=p%
p%=INSTR(p.name$,"\",po%+1)
WEND
path$=LEFT$(p.name$,po%)
prg.name$=MID$(p.name$,po%+1)
ELSE
@ende
ENDIF
CLS
PRINT
PRINT
PRINT " Erstelle Cross-Reference-Listing von ";prg.name$;".LST"
PRINT
PRINT " Zeilen-Nummer in Bearbeitung -> ";
y%=CRSLIN
x%=CRSCOL
akt_zeile%=0
first!=TRUE
DEFMOUSE 2
WHILE (NOT EOF(#1)) AND INKEY$<>CHRS(27)
INC akt_zeile%
LINE INPUT #1,basic_zeile$
PRINT AT(x%,y%);akt_zeile%;
@entf_leer
@entf_str_const
@entf_komm
@suche_namen
WEND
@namen_abspeichern
DEFMOUSE 0
CLOSE
RETURN! Bearbeiten
'
'
PROCEDURE entf_leer ! FĂŒhrende Leerzeichen einer Basic-Zeile entfernen
WHILE MID$ (basic_zeile$,1,1)=" "
basic_zeile$=MID$(basic_zeile$,2)
WEND
RETURN! Entf_leer
'
'
PROCEDURE entf_str_const ! Entf. von in AnfĂŒhrungszeichen stehenden Strings
CLR l%
REPEAT
k%=INSTR(basic_zeile$,CHRS(34),l%+1) ! Position des " am String-Anfang
l%=INSTR(basic_zeile$,CHRS(34),k%+1) ! Position des " am String-Ende
IF (k%<>0) AND (l%>k%)
basic_zeile$=LEFT$(basic_zeile$,k%)+MID$(basic_zeile$,l%)
l%=k%+1
ENDIF! (k%<>0) And (l%>k%)
UNITL k%=0 OR k%>l%
RETURN! Entf_str_const
'
'
PROCEDURE entf_komm ! Entf. von Kommentaren die hinter "!"-Zeichen stehen
CLR k% ! Position eines '!'
ok!=FALSE ! Flag fĂŒr erfolgreiche Suche nach Kommentar-'!'
REPEAT
k%=INSTR(basic_zeile$,k%+1)
IF (k%>2) ! Kommentare nur ab der 3. Spalte möglich
' Das Zeichen vor dem '!' ist , ')', âoder ' '
komm_found!=INSTR(CHR$(34)+"),; ",MID$(basic_zeile$,k%-1,1))
IF (INSTR(basic_zeile$,"!!"))
' ______________________________________________________
â Das Kommentar-'!' steht direkt hinter einer bool'schen Variablen
' ______________________________________________________
komm_found!=TRUE
k%=INSTR(basic_zeile$,"!!")+1
ENDIF! (Instr(Basic_zeile$,"!!"))
IF komm_found! ! Kommentar mit Sicherheit gefunden
basic_zeile$=LEFT$(basic_zeile$,k%-1)
ok!=TRUE
ELSE ! Weitere ĂberprĂŒfung notwendig
i%=k%
REPEAT ! Suche Anfang des
DEC i% ! Bezeichners vor
pch$=MIDS(basic_zeile$,i%,1) ! dem '!'
UNTIL (INSTR(trenn_zei$,pch$)<>0) OR i%=1 !
IF (i%>1)
INC i%
ENDIF! (i%>1)
IF ((k%>i%) AND (i%>0))
' ______________________________________
' Ermittlung des Bezeichners vor dem â!â
' ______________________________________
ident$=MID$(basic_zeile$,i%,k%-i%)
§ident_name ! Teste, ob GfA-SchlĂŒsselwort
IF (NOT ident_name!)
' ___________________________________________
' SchĂŒsselwort oder Zahlenkonstante liegt vor
' ___________________________________________
ok!=TUE
basic_zeile$=LEFTS(basic_zeile$,k%-1)
ENDIF! (Not Search!)
ENDIF! ((K%>I%) And (I%>0))
ENDIF! Komm_found!
ENDIF! (K%>2)
UNTIL (ok! OR k%=0)
RETURN! Entf_komm
'
'
PROCEDURE suche_namen ! Suche Bezeichner, die nicht GfA-Basic-SchlĂŒsselwort
LET o_flag!=FALSE ! On ... gefunden
LET lab_flag!=FALSE ! Label gefunden
LET up_nam_found!=FALSE ! Unterprogrammnamen gefunden
LET name_pos!=FALSE ! Name ĂŒberhaupt möglich
LET b$="§()= :"
FOR i%=1 TO LEN(b$)
LET name_pos!=name_pos! OR INSTR(basic_zeile$,MID$(b$,i%,1))
NEXT i%
LET name_pos!=name_pos! AND NOT ((MID$(basic_zeile$,1,1)="'") OR (MID$(basic_zeile$,1,3)="REM") OR (MID$(basic_zeile$,1,4)="DATA"))
IF name_pos!
' _______________________________________________________________
' Basic-Zeile enthÀlt mindestens 1 Anweisung mit Klammern oder
' Gleichheitszeichen und ist nicht Kommentar- oder Data-Zeile
' _______________________________________________________________
WHILE LEN(basic_zeile$)>0
§identifier
IF first!
§test_list_def(ident$)
ENDIF
IF ident$="ON"
o_flag!=TRUE
ENDIF
IF ident$="PROCEDURE" OR ident$="GOSUB" OR ident$="§"
' Unterscheidung zw. Variablen und Unterprg.-namen bzw. Labeln
up_nam_found!=TRUE
ENDIF! Ident$="PROCEDURE" Or ...
IF ident$="GOTO" OR ident$="RESUME" OR ident$="RESTORE"
lab_flag!=TRUE
ENDIF
§ident_name
IF ident_name!
IF up_nam_found!
t%=4
§name_ablegen
IF NOT o_flag!
up_nam_found!=FALSE
ENDIF
ELSE
IF INSTR(ident$,":") OR lab_flag!
IF RIGHT$(ident$,1)=":"
ident$=LEFT$(ident$,LEN(ident$)-1)
ENDIF
t%=5
§name_ablegen
lab_flag !=FALSE
ELSE
t%=INSTR("%$!",RIGHT$(ident$,1)) ! Typ der Variablen
§name_ablegen
ENDIF
ENDIF
ENDIF
WEND
ENDIF
RETURN! Such_namen
'
'
PROCEDURE identifier
' Ermittlung des nÀchsten Bezeichners Beginnend ab der 1. Spalte
' des restlichen Basic-Zeilen-Strings; der gefundene Bezeichner
' wird anschlieĂend von diesem entfernt.
CLR ident$,nch$,i%
REPEAT
INC i%
ident$=ident$+nch$
nch$=MID$(basic_zeile$,i%,1)
EXIT IF nch$="§"
UNITL INSTR(trenn_zei$,nch$)<>0 OR nch$=""
IF nch$="§"
ident$="§"
ENDIF! Nch$="S"
k%=ABS(basic_zeile$,1%,1)="(") ! Funktion oder Array-Var
basic_zeile$=MID$(basic_zeile$,i%+1) ! Der verbleibende String
RETURN ! Identifier
'
'
PROCEDURE ident_name ! Identifiziere Namen, falls Erfolg Ident_name!=TRUE
IF LEN(ident$)=0 OR ident$="§"
ident_name!=FALSE
ELSE
IF VAL?(ident$)=LEN(ident$)
' ------------------------
' Zahlenkonstante gefunden
' ------------------------
ident_name!=FALSE
ELSE
IF LEN(ident$)>1
' ---------------------------------------------
' SchlĂŒsselworte sind mindestens 2 Zeichen lang
' ---------------------------------------------
f_ch$=LEFT$(ident$)
IF f_ch$>="a" AND f_ch$<="z"
ident_name!=TRUE
ELSE
ident_name!=FALSE
ENDIF
ELSE! (Len(Ident$)=1)
' -----------------------------------------------------------------
' BezeichnerlÀnge gleich eins bedeutet immer einfache Real-Variable
' -----------------------------------------------------------------
ident_name!=TRUE
ENDIF! Len(Ident$)>1
ENDIF! Val?(Ident$)=Len(Ident$)
ENDIF! Len(Ident$)=0
RETURN
'
'
PROCEDURE test_list_def(test$)
zw_buchst!=FALSE
f_ch$=LEFT$(test$)
FOR i%=2 TO LEN(test$)
n_ch$=UPPER$(MID$(test$,i%,1))
zw_buchst!=(n_ch$>="A" AND n_ch$<="Z")
EXIT IF zw_buchst!
NEXT i%
IF zw_buchst!
IF f_ch$>="A" AND f_ch$<="Z"
IF UPPER$(test$)<>test$
meldung$="Das Program ist nicht mit| 'DEFLIST 0' abgespeichert ! |XREF abbrechen nachladen von |"+prg.name$+".BAS ?"
ALERT 2,meldung$,2," Nö | Klar ",antw%
IF antw%=2
meldung$="Nach Abspeichern mit 'SAVE, A' |XREF.BAS mit 'LOAD' aufrufen|und erneut starten !"
ALERT 1,meldung$,1," Ok ",dummy%
l_name$=MID$(prg.name$,LEN(prg.name$)-4)+".BAS"
IF NOT (EXIST(l_name$))
ALERT 2, "BAS-File nicht gefunden",1, "Mist",dummy%
FILESELECT "\*.BAS","",l_name$
ENDIF
IF l_name$<>""
LOAD l_name$
ELSE
ALERT 2, "Program nicht gefunden.|Neustart",1," Seufz ",dummy%
RUN
ENDIF
ELSE
RUN
ENDIF! antw%=2
ENDIF! UPPER$(test$)<>test$
ENDIF! f_ch$>="A" AND ...
first!=FALSE
ENDIF! zw_buchst!
RETURN! test_list_def
'
'
PROCEDURE name_ablegen ! Ablegen der identifizierten Namen in einem Feld
id_len%=LEN(ident$)
IF id_len%>0
IF k%=1
ident$=ident$+"()"
INC id_len%
INC id_len%
ENDIF
IF id_len%<20
ident$=ident$+STRING$(20-id_len%,".")
ELSE
IF id_len%>20
ident$=ident$+CHR$(10)+CHR$(13)+SPACE$(20)
ENDIF
ENDIF
zei_nr$=" "
RSET zei_nr$=STR$(akt_zeile%)
IF max_nam_anz%(t%,k%)=0 AND zeilen_zaehler%(t%,k%,1)=0
' ------------------------------
' Erster Aufruf dieser Procedure
' ------------------------------
LET name$(t%,k%,1)=ident$ ! Der gefundene Name
LET zeile$(t%,k%,1)=zei_nr$ ! und die zugehörige Zeile
zeilen_zaehler%(t%,k%,1)=1 ! Anzahl Zeilen in denen Name$() enth. ist
max_nam_anz%(t%,k%)=1
ELSE
' --------------------
' Jeder weitere Aufruf
' --------------------
FOR i%=1 TO max_nam_anz%(t%,k%) ! Suchen ob Ident$ schon mal aufgetaucht?
EXIT IF name$(t%,k%,i%)=ident$ ! falls ja, Aussteigen aus der Schleife
NEXT i% !
IF i%>max_nam_anz%(t%,!c%)
' ---------------------------------
' Ident$ ist noch nicht vorgekomnen
' ---------------------------------
INC max_nam_anz%(t%,k%) ! ZÀhler erhöhen
INC zeilen_zaehler%(t%,k%,i%) ! ZeilenzÀhler=1
LET name$(t%,k%,i%)=ident$ ! Neuen Name speichern
LET zeile$(t%,k%,i%)=zei_nr$ ! 1. Zeile ablegen
ELSE
IF INSTR(zeile$(t%,k%,i%),zei_nr$)=0
' ---------------------------------------------
' Ident$ kennt in Akt_zeile% zum ersten mal vor
' ---------------------------------------------
INC zeilen_zaehler%(t%,k%,i%)
LET zeile$(t%,k%,i%)=zeile$(t%,k%,i%)+zei_nr$
ENDIF
ENDIF! (I%>max_nam_anz%(t%,k%)
ENDIF
ENDIF
RETURN! Name_ablegen
'
'
PROCEDURE namen_abspeichern
FOR k%=0 TO 1
FOR t%=0 TO typ_max%
IF max_nam_anz%(t%,k%)>0
§sort(t%,k%,max_nam_anz%(t%,k%))
ENDIF
NEXT t%
NEXT k%
RETURN! namen_abspeichern
'
'
PROCEDURE sort(typ%,kl%,m_v_anz%) ! Alphabetisches Sortieren der Namen
LOCAL i%,j%
FOR i%=m_v_anz%-1 DOWNTO 0
FOR j%=0 TO i%
IF name$(typ%,kl%,j%)>name$(typ%,kl%,j%+1)
SWAP name$(typ%,kl%,j%),name$(typ%,kl%,j%+1)
SWAP zeile$(typ%,kl%,j%),zeile$(typ%,kl%,j%+1)
SWAP zeilen_zaehler%(typ%,kl%,j%),zeilen_zaehler%(typ%,kl%,j%+1)
ENDIF
NEXT j%
NEXT i%
RETURN! Sort()
'
'
PROCEDURE ausgabe
ALERT 2,"Ausgabe auf ",1,"Schirm|Drucker",device%
IF device%=1
device$="CON:"
ELSE
device$="PRN:"
ENDIF
ALERT 1,"Ausgabe von ",1,"XRF-List|PRG-List",antw%
CLS
IF antw%=1
@namen_ausgeben(device$)
ELSE
@listing_ausgeben(device$)
ENDIF
RETURN! Ausgabe
'
'
PROCEDURE namen_ausgeben(dev$)
OPEN "O",#3,dev$
PRINT #3
PRINT #3," Cross-Reference-Listing von ";prg.name$;".LST"
PRINT #3,STRINGS(80,"=")
scrn_l%=CRSLIN-1
FOR t%=0 TO typ_max%
IF max_nam_anz%(t%,0)+max_nam_anz%(t%,1)>0
PRINT #3
@halt
PRINT #3,"* ";typ$(t%)
@halt
ENDIF
FOR k%=0 TO 1
FOR i%=1 TO max_nam_anz%(t%,k%)
IF LEN(name$(t%,k%,i%))>0
PRINT #3,name$(t%,k%,i%);
IF LEN(name$(t%,k%,i%))>20
@halt
ENDIF
IF LEN(zeile$(t%,k%,i%))>sp_anz_zeil%
za%=(LEN(zeile$(t%,k%,i%)) DIV sp_anz_zeil%)
IF LEN(zeile$(t%,k%,i%)) MOD sp_anz_zeil%>0
za%=za%+1
ENDIF
PRINT #3,MIDS(zeile$(t%,k%,i%),1,sp_anz_zeil%)
@halt
z%=1
WHILE z%<za%
PRINT #3,SPACES(20);MID$(zeile$(t%,k%,i%),sp_anz_zeil%*z%+1,sp_anz_zeil%)
@halt
INC z%
WEND
ELSE
PRINT #3,zeile$(t%,k%,i%)
@halt
ENDIF
ENDIF
NEXT i%
NEXT k%
NEXT t%
CLOSE
PRINT CHR$(7)
IF device%<>0
PRINT " ";inv_einS;" Ausgabe beendet - ZurĂŒck durch Tastendruck! ";inv_aus$
PAUSE 20
REPEAT
UNITL INKEY$<>"" OR MOUSEK
ENDIF
RETURN! Namen_ausgeben()
'
PROCEDURE listing_ausgeben(dev$)
OPEN "I",#1,p.name$+".LST"
OPEN "O",#2,dev$
PRINT #2
PRINT #2," Nummeriertes Listing von ";p.name$+".LST"
PRINT #2,STRINGS(80,"=")
PRINT #2
scrn_l%=CRSLIN
akt_zeile%=0
WHILE (NOT EOF(#1)) AND INKEY$<>CHR$(27)
INC akt_zeile%
LINE INPUT #1,basic_zeile$
PRINT #2,USING "###: ",akt_zeile%;
PRINT #2,basic_zeile$
§halt
WEND
CLOSE
PRINT CHR$(7)
IF device%<>0
PRINT " ";inv_ein$;â Ausgabe beendet - ZurĂŒck durch Tastendruck! ";inv_aus$
PAUSE 20
REPEAT
UNTIL INKEY$<>"" OR MOUSEK
ENDIF
RETURN
'
'
PROCEDURE halt
IF device%=1
IF ASC(INKEY$)=19 ! Anhalten mit âS
PAUSE 10
REPEAT
UNTIL ASC(INKEY$)=17 ! Weiter mit 'Q
ENDIF
INC scrn_l%
IF scrn_l%>20
scrn_l%=0
PRINT CHRS (7)
PRINT SPACES(21);inv_ein$;" Weiter durch DrĂŒcken einer Taste! ";inv_aus$
REPEAT
UNTIL INKEY$<>"" OR MOUSEK
PRINT
ENDIF
ENDIF
RETURN
'
'
PROCEDURE ende
IF prg.name$<>""
device%=0
ALERT 3,"Arbeit sichern?",1," Nö | Klar ",antw%
IF antw%=2
TEXT 125,30," Arbeit sichern. "
FILESELECT path$+"*.CRF",prg.name$+".CRF",crf$
IF crf$<>""
DEFMOUSE 2
@namenausgeben(crf$)
DEFMOUSE 0
ENDIF
FILESELECT path$+"*. PRN",prg.name$+".PRN",prn$
IF prn$<>""
DEFMOUSE 2
@listing_ausgeben(prn$)
DEFMOUSE 0
ENDIF
ENDIF
ENDIF
CLS
END
RETURN
Listing 1