← ST-Computer 05 / 1987

Cross-Referenz-Listings in GFA-Basic

Listings

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