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