»Dokumentationshilfe«: Cross-Referenz-Liste C 64
Die Dokumentation von Basic-Programmen ist manchmal eine mühevolle Aufgabe. Trotzdem ist sie sinnvoll oder sogar notwendig. Aus diesem Grund schlugen wir einen Programmierwettbewerb zur Dokumentationshilfe vor, dessen Ergebnis hier vor Ihnen liegt.
Jeder Basic-Programmierer kennt das Problem: Man schreibt lange und auch komplizierte Programme und merkt, daß langsam aber sicher der Überblick verloren geht. Oder auch, wenn versucht wird, ein fremdes, nicht selbst erstelltes Programm zu verstehen: Was bedeutet diese Variable, und wo taucht sie im Programm noch auf? Von wo und wie oft wird diese Programmzeile angesprungen?
Bei gut strukturierten Programmen findet man relativ schnell durch, aber die sind selten zu finden. Doch selbst dann ist eine gute Beschreibung eine hilfreiche Sache und erleichtert den späteren Wiedereinstieg.
Eine Crossreferenzliste unterstützt bei der Erstellung einer Dokumentation. Gerade bei kommerziellen Software-Projekten wird nicht auf sie verzichtet. Sie enthält dabei nicht nur eine Aufzählung aller Sprünge (sofern bei höheren Programmiersprachen überhaupt vorhanden), sondern vor allem eine komplette Variablenliste. Und diese Funktionen hat auch unser Programm. Im einzelnen enthält sie:
- Liste aller Zeilen, in denen Sprungbefehle enthalten sind. Angegeben wird die Zeilennummer und die angesprungene Zeile.
- Liste aller Zeilen, die angesprungen werden. Angegeben sind die Zeilennummer und alle Zeilen, von denen aus diese Zeile angesprungen wird. Eine sehr nützliche Information.
- Eine Liste aller im Programm vorkommenden Variablen. Angegeben wird, alphabetisch sortiert, der Variablenname und die Zeilen, in denen sie vorkommt. Zusätzlich hat man die Möglichkeit, zu jeder Variablen einen kurzen Kommentar mit einzugeben.
- Liste aller Variablen mit dem vorhin genannten Kommentar, aber ohne Hinweise auf die Zeilen, in denen sie vorkommen.
Mit diesen Informationen kann man schon etwas anfangen. Doch jetzt etwas für Programmierer selbst:
Wie funktioniert das?
Wie sucht man nach Variablen? Wie nach Sprungbefehlen? Wie so oft, ist die Lösung ganz einfach.
Wie ein Basic-Programm im Speicher steht, wurde ausführlich im 64’er, Ausgabe 2/85, Seite 87, beschrieben. Fast in der gleichen Form wird ein Programm auch auf Diskette gespeichert. Lediglich zwei Byte für die Ladeadresse des Programms kommen dazu. Beim absoluten Laden eines Programms (LOAD"NAME",8,1) holt sich der Basic-Interpreter diese zwei Byte, um festzustellen, an welcher Stelle im Speicher das Programm beginnt. Beim normalen LOAD-Befehl, also ohne die ",1" am Ende, wird jedes Programm ab Basic-Anfang gesetzt, also ab $801 ( = dezimal 2049).
An einem kleinen Beispiel soll gezeigt werden, wie man sich alle Zeilen anzeigen lassen kann, die den Befehl GOTO enthalten. Sehen Sie sich dazu das Flußdiagramm (Bild 1) an.

Als erstes muß eine »Programmdatei« geöffnet werden. Das geht zum Beispiel mit OPEN2,8,2,"NAME,P,R". »P« steht dabei für Programm und »R« für READ, also lesen.
Dann folgt das Lesen der Byte. Dazu nimmt man den GET#-Befehl. Der INPUT #-Befehl ist zwar wesentlich schneller, »schafft« aber nur maximal 88 Zeichen, benötigt ein Carriage Return (CHR$(13)) als Endekennzeichen (bei Programmen sind es Null-Byte) und verträgt keine Kommata (in Basic-Programmen oft vorhanden). Außerdem erlaubt uns der GET#-Befehl die Überprüfung einzelner Zeichen direkt nach dem Laden.
Lesen wir jetzt also die ersten Byte eines Basic-Programms. Am Anfang, das sind die ersten zwei Bytes, steht die Startadresse. Bei Basic-Programmen ist sie 2049, hexadezimal ausgedrückt $0801. Darauf folgen zwei Byte für die Anfangsadresse der nächsten Basic-Zeile (gemeint ist hier die Adresse im RAM, die natürlich auch gespeichert wird). Erst jetzt beginnt die wirkliche Basic-Zeile: Zwei Byte für die Zeilennummer, aufgetrennt in Low- und High-Byte, gefolgt vom Basic-Text. Das Ende einer Basic-Zeile erkennen Sie (und der Basic-Interpreter) an einem Null-Byte. Danach folgt wieder die Anfangsadresse der nächsten Basic-Zeile (Low- und High-Byte) und der nächsten Zeilennummer (auch Low- und High-Byte), danach der Basic-Text, etc., etc.. Das Ende eines Basic-Programms ist gekennzeichnet durch drei aufeinanderfolgende Null-Byte, und auch die Statusvariable ST wird auf 64 gesetzt.
Das war’s schon. Mit diesem Wissen sind Sie in der Lage, ein Basic-Programm komplett zu analysieren. Eine vollständige Liste aller Token, das sind die Abkürzungen, die Codes der Basic-Befehle, finden Sie in Bild 2.
Befehl | Token | Befehl | Token | Befehl | Token | |||
---|---|---|---|---|---|---|---|---|
DEZ | HEX | DEZ | HEX | DEZ | HEX | |||
END | 128 | 80 | CONT | 154 | 9A | SGN | 180 | B4 |
FOR | 129 | 81 | LIST | 155 | 9B | INT | 181 | B5 |
NEXT | 130 | 82 | CLR | 156 | 9C | ABS | 182 | B6 |
DATA | 131 | 83 | CMD | 157 | 9D | USR | 183 | B7 |
INPUT# | 132 | 84 | SYS | 158 | 9E | FRE | 184 | B8 |
INPUT | 133 | 85 | OPEN | 159 | 9F | POS | 185 | B9 |
DIM | 134 | 86 | CLOSE | 160 | A0 | SQR | 186 | BA |
READ | 135 | 87 | GET | 161 | Al | RND | 187 | BB |
LET | 136 | 88 | NEW | 162 | A2 | LOG | 188 | BC |
GOTO | 137 | 89 | TAB | 163 | A3 | EXP | 189 | BD |
RUN | 138 | 8A | TO | 164 | A4 | COS | 190 | BE |
IF | 139 | 8B | FN | 165 | A5 | SIN | 191 | BF |
REST. | 140 | 8C | SPC | 166 | A6 | TAN | 192 | C0 |
GOSUB | 141 | 8D | THEN | 167 | A7 | ATN | 193 | C1 |
RETURN | 142 | 8E | NOT | 168 | A8 | PEEK | 194 | C2 |
REM | 143 | 8F | STEP | 169 | A9 | LEN | 195 | C3 |
STOP | 144 | 90 | + | 170 | AA | STR$ | 196 | C4 |
ON | 145 | 91 | — | 171 | AB | VAL | 197 | C5 |
WAIT | 146 | 92 | * | 172 | AC | ASC | 198 | C6 |
LOAD | 147 | 93 | / | 173 | AD | CHR$ | 199 | C7 |
SAVE | 148 | 94 | ↑ | 174 | AE | LEFT$ | 200 | C8 |
VERIFY | 149 | 95 | AND | 175 | AF | RIGHT$ | 201 | C9 |
DEF | 150 | 96 | OR | 176 | B0 | MID$ | 202 | CA |
POKE | 151 | 97 | > | 177 | Bl | GO | 203 | CB |
PRINT# | 152 | 98 | = | 178 | B2 | |||
153 | 99 | < | 179 | B3 |
Doch nun zum Programm »Cross-Ref 64«.
Cross-Ref 64
Das hier vorgestellte Programm Cross-Ref 64 (Listing 2) arbeitet auf allen Commodore-Computern, mit einem Floppy- oder Kassetten-Laufwerk und einem Drucker.
Nach dem Start mit RUN kann die Ausgabe der fertigen Listen auf Drucker oder Bildschirm gewählt werden (siehe auch Zeile 200 bis 260). Die Variable DV enthält die jeweils gültige Gerätenummer. Danach müssen Sie den Dateinamen des zu durchsuchenden Files eingeben.
Ihr Computer liest sich nun das Programm wie ein sequentielles File Byte für Byte durch. Das Programm steht auf der Diskette im gleichen Format wie im RAM, lediglich die ersten beiden Bytes geben den Start des Programms an. Liegt Ihr Programm nicht ab 2049, zum Beispiel bei Maschinenprogrammen üblich, so bricht das Programm die Bearbeitung ab. Sollten Sie das Programm auf einem anderen Commodore-Computer betreiben, so geben Sie einfach Listing 3 ein und tauschen die auf dem Bildschrim erscheinende Zahl gegen die in Zeile 290 stehende 2049 aus.
Beim Durchsuchen »hangelt« sich der Computer durch die Zeilen, indem er, immer wenn er auf einen Charakter 0 trifft, eine neue Zeile beginnt. Im ersten Paß durchsucht er das Programm auf die Token für die Sprungbefehle GOTO, RUN, GOSUB und THEN (Zeile 330). Sie haben, in der gleichen Reihenfolge, die ASCII-Werte 137, 138, 141 und 167. Gefundene Sprünge werden auf zwei Arten gespeichert. Als erstes im Format ZEILENNUMMER : SPRUNGZIEL (Bild 3) in den Zeilen 400 bis 420, darauf im Format SPRUNGZIEL : ZEILENNUMMERN (Bild 3). Wobei SPRUNGZIEL immer die Zahl ist, die hinter GOTO oder THEN steht. ZEILENNUMMER ist jeweils die Zeile, in der der Sprungbefehl auftaucht.

Die Zeilennummern werden in den Feldern ps$( und sp$( gespeichert. Wird in einem der Felder die Maximallänge von 70 Zeichen überschritten, so wird ein neues Feld angelegt (Zeile 410 und 450).
Haben Sie den ersten Pass glücklich überstanden, so wird die Liste auf das von Ihnen gewählte Gerät übertragen (Zeile 580 bis 680). Die aufsteigend sortierten Zeilennummern werden so ausgegeben, daß nie zwei gleiche Zeilennummern untereinanderstehen. Statt dessen werden sechs Leerzeichen gedruckt (Zeile 660).
Der zweite Pass läuft im Prinzip wie der erste ab. Die Variablen mit zugehörigen Zeilennummern werden lediglich im Feld VA$( gespeichert und sortiert. Selbstverständlich wird der Text hinter REM und DATA sowie Anführungszeichen überlesen (Zeile 750-790).
In diesem Stadium kommt Arbeit auf Sie zu. Jetzt haben Sie die Möglichkeit, die Variablen mit einem Text zu versehen. Dabei müssen Sie sich allerdings auf 25 Zeichen beschränken.
Antworten Sie auf die Frage »WOLLEN SIE ZU DEN VARIABLEN BEMERKUNGEN EINGEBEN« mit »J«, so können sie mit Cursor UP und DOWN durch die Liste wandern. Die momentane Variable wird jeweils angezeigt. Haben Sie die richtige Stelle gefunden, so drücken Sie »RETURN« und können Ihren Text eingeben. Durch nochmaliges Drücken der »RETURN«-Taste wird der Text gespeichert.
Haben Sie Ihre Texte verteilt, so beginnt das Programm nach Drücken der »N«-Taste zu drucken (Zeilen 1180 bis 1280). Auch hier können Sie den Druckvorgang wiederholen (Bild 4 und 5).


Umstellungshinweise
Dieses Programm ist auf allen Computern lauffähig, die ihren Basic-Text nach Commodoreart speichern. Potentielle »Umschreiber« können sich daher auf die Zeile 330 beschränken. Auch die Druckausgabe ist leicht modifizierbar. Die Sekundäradresse der OPEN-Befehle in den Zeilen 580 und 1180 stellen den Epson MX-80 über das Print-64-Interface auf Klein/Großschrift um. Dies kann auch durch normale »OPENs« mit nachfolgenden Steuercodes erfolgen.
Sogar bei einem Kassettenlaufwerk ist das Programm verwendbar. Auch hier ändern Sie in den Zeilen 290 und 730 die OPEN-Befehle (Listing 4).
(Stefan Becker/gk)5 REM X=2 10 DIMA$(100),B(20),C%(20) 20 FORI=1TO20:B(I)=I:NEXT 30 FORJ=1TO20 40 PRINT" WERT NR."J;:INPUTA$(J) 50 IF A$(J)="X" THEN J=20 60 NEXT J 70 INPUT"PRG.NAME = "; NA$ 80 OPEN2,8,2,NA$+"P,W" 90 FORX=1TOJ 100 PRINT#2,A$(I) 110 NEXT 120 CLOSE2 130 INPUT"AUSWAHL =";AW$:AW=VAL(AW$) 140 ON AW GOSUB 1000,2000,3000 150 END 1000 PRINTAW:X=1:RETURN 2000 PRINTAW:X=2:RETURN 3000 PRINTAW:X=3:RETURN
10 rem********************************* 20 rem* programmname : xref * 30 rem* c-64 * 40 rem* floppy 1541 o. aehnliche * 50 rem* drucker (z.b. mps 801) * 60 rem* von stefan becker * 70 rem********************************* 80 clr:goto150 90 get#1,a$:x=asc(a$+n$):if(64andst)=0thenreturn 100 close1:ifpathenpa=0:goto520 110 goto960 120 gosub90:x1=x:gosub90:x=x1+256*x:return 130 ifsp<obandva<obandps<obthenreturn:rem*** grenzen erreicht ? *** 140 print:print"Bitte die Variable ob in Zeile 150 vergroessern.":goto1310 150 ob=500:dimsp$(ob),ps$(ob),va$(ob) 160 rem*** ob ist obergrenze der anzahl der spruenge und variablen *** 170 poke53280,6:poke53281,6 180 n$=chr$(0) 190 le$=" " 200 printchr$(147)chr$(9)chr$(14)chr$(8)chr$(144); 210 print"{rvon} Cross-Referenz-Lister " 220 print"{down}{down}Ausgabe auf ":print"{down}{rvon}B{rvof}ildschirm oder {rvon}D{rvof}rucker{up}{up}{up}" 230 printspc(12);:poke204,0 240 geta$:ifa$<>"b"anda$<>"d"goto240 250 ifa$="b"thenprint"Bildschirm.":dv=3:goto270 260 print"Drucker.":dv=4 270 print"{down} {up}" 280 open1,0:print"Programmname: ";:input#1,na$:close1:print 290 open2,8,15:open1,8,2,na$+",p,r":gosub1290:gosub120:ifx=2049then310 300 print:print"Das Programm muss ab 2049 liegen.":goto1310 302 : 304 : 305 rem******************************** 306 rem pass 1 sprungtabelle 308 rem******************************** 309 : 310 print"{clr}Pass 1 (Suchen der Spruenge){down}":pa=1 320 gosub120:gosub120:ze$=right$(" "+str$(x),5):print"{home}{down}{down}"ze$ 330 gosub90:ifx=137orx=138orx=141orx=167thens1$="":goto360 340 ifx=0goto320 350 goto330 360 gosub90:ifx=32goto360 370 ifx>=48andx<=57thens1$=s1$+a$:goto360 380 ifs1$=""goto470 390 s1$=right$(" "+s1$,6) 400 ifleft$(sp$(sp),5)<>ze$thensp=sp+1:gosub130:sp$(sp)=ze$+":" 410 iflen(sp$(sp))>70thensp=sp+1:gosub130:sp$(sp)=ze$+":" 420 ifright$(sp$(sp),6)<>s1$thensp$(sp)=sp$(sp)+s1$ 430 fori=1tops 440 ifleft$(ps$(i),6)<>s1$thennext:ps=i:gosub130:ps$(i)=s1$+":" 450 iflen(ps$(i))>70thennext:ps=ps+1:i=ps:gosub130:ps$(i)=s1$+":" 460 ifright$(ps$(i),5)<>ze$thenps$(i)=ps$(i)+" "+ze$ 470 ifx=0goto320 480 ifx=44thens1$="":goto360 490 ifx=58orx>=127or(x>=65andx<=90)goto330 500 print:print"{down}Fehler im Quellprogramm. Zeile:"ze$:goto1310 502 : 504 : 506 rem ------------------------------- 510 rem*** sortieren der sprungziele (feld ps$( *** 515 rem ------------------------------- 516 : 520 fori=1tops 530 forj=itops 540 ifleft$(ps$(i),5)<left$(ps$(j),5)goto560 550 ps$(0)=ps$(i):ps$(i)=ps$(j):ps$(j)=ps$(0) 560 nextj 570 nexti 574 : 575 rem-------------------------------- 576 rem ausgabe spruenge + sprungziele 577 rem-------------------------------- 578 : 580 open1,dv,7:print#1,"Programmname: "na$:print#1 590 print#1,"sprungtabelle " 600 print#1,"--------------------------" 605 print#1,"zeile : sprung auf zeile ":print#1 610 fori=1tosp 620 ifleft$(sp$(i-1),5)=left$(sp$(i),5)thenprint#1,spc(7)mid$(sp$(i),7):goto640 630 print#1," "sp$(i) 640 nexti:print#1 645 print#1,"zeile : wird angesprungen von":print#1 650 ps$(0)="":fori=2tops 660 ifleft$(ps$(i-1),6)<>left$(ps$(i),6)thenprint#1,ps$(i):goto680 670 print#1,spc(6);mid$(ps$(i),7) 680 nexti:close1 690 print"{down}{rvon}N{rvof}ochmals/{rvon}W{rvof}eiter" 700 geta$:ifa$<>"n"anda$<>"w"goto700 710 ifa$="n"goto580 712 : 714 : 715 rem******************************** 716 rem pass 2 variable suchen 718 rem******************************** 719 : 720 print"{clr}Pass 2 (Suchen der Variablen){down}" 730 open1,8,2,na$+",p,r":gosub1290:gosub120 740 gosub120:gosub120:ze$=right$(" "+str$(x),5):print"{home}{down}{down}"ze$:y=0 750 gosub90 760 ifx=0goto740 770 ifx=34orx=131orx=143goto800 780 ifx>64andx<91goto850 790 goto750 800 ifx=131theny=1 810 gosub90:ifx=0goto740 820 ifx=34goto750 830 ifx=58andy=1goto750 840 goto810 850 v1$=a$ 860 gosub90 870 ifx=36orx=37or(x>47andx<58)or(x>64andx<91)thenv1$=v1$+a$:goto860 880 ifx=40thenv1$=v1$+a$ 890 v1$=left$(v1$+" ",4) 900 fori=1tova 910 ifleft$(va$(i),4)<>v1$thennext:va=i:gosub130:va$(i)=v1$+" :" 920 iflen(va$(i))>50thennext:va=va+1:i=va:gosub130:va$(i)=va$+" :" 930 ifright$(va$(i),5)<>ze$thenva$(i)=va$(i)+" "+ze$ 940 ifx=0goto740 950 goto750 952 : 954 : 956 rem ------------------------------- 960 rem *** sortieren der variablen (feld va$( *** 965 rem ------------------------------- 968 : 970 fori=1tova 980 forj=itova 990 ifleft$(va$(i),4)>left$(va$(j),4)goto1010 1000 va$(0)=va$(i):va$(i)=va$(j):va$(j)=va$(0) 1010 nextj 1020 nexti:va=va-1:i=va 1025 : 1030 print"{clr}Wollen Sie zu den Variablen":print"Bemerkungen eingeben? "; 1040 geta$:ifa$="n"goto1180 1050 ifa$<>"j"goto1040 1060 print"Ja{down}{down}{down}" 1070 print"{home}{down}{down}{down}{down}{down}{down}Variablenname: ";left$(va$(i),4) 1073 printle$ 1075 iflen(va$(i))>70thenprint"{up}{up}"right$(va$(i),25) 1080 geta$:ifa$<>"{up}"anda$<>"{down}"anda$<>chr$(13)goto1080 1090 ifa$="{up}"theni=i+1:ifi>vatheni=va 1100 ifa$="{down}"theni=i-1:ifi<1theni=1 1110 ifa$<>chr$(13)goto1070 1120 vr$=left$(va$(i),4):open1,0 1130 print"Text:";:input#1,te$:print:close1:te$=left$(te$,25) 1140 fori=vato1step-1 1150 ifleft$(va$(i),4)<>vr$thennext:print"Nicht vorhanden.":goto1070 1160 va$(i)=left$(va$(i)+le$,55)+right$(le$+te$,25):goto1030 1170 vr$=left$(vr$,4) 1174 ; 1175 rem------------------------------- 1176 rem ausgabe variable 1177 rem------------------------------- 1180 print:open1,dv,7 1190 print#1,"{down}{down}liste der variablen :" 1200 print#1,"-------------------------" 1210 fori=vato1step-1 1220 ifleft$(va$(i+1),4)<>left$(va$(i),4)thenprint#1,va$(i):goto1240 1230 print#1,spc(6);mid$(va$(i),7) 1240 nexti 1250 print"{down}{rvon}n{rvof}ochmals/{rvon}w{rvof}eiter" 1260 geta$:ifa$<>"n"anda$<>"w"goto1260 1263 ifa$="n"goto1190 1266 print"Variablenliste ohne Zeilennummer (j/n)" 1267 getr$:ifr$=""then1267 1268 ifr$<>"j"thenprint:print"ende":goto1310 1269 print#1:print#1:print#1,"Variablenliste ohne Zeilennummer" 1270 print#1,"-------------------------------" 1271 fori=vato1step-1 1272 iflen(va$(i))>70thenprint#1,left$(va$(i),8);right$(va$(i),25):goto1274 1273 print#1,left$(va$(i),8) 1274 nexti 1280 goto1310 1290 input#2,a$,b$:ifa$="00"thenreturn 1300 print:print"Disk-Error: ";b$ 1310 close1:close2:end
Eingetippt von vicjack 10 input"name";a$ 20 open 1,8,2,a$+",p,r" 30 get#1,a$,b$ 40 close 1 50 print "anfangsadresse: " ; asc(a$+chr$(0))+256*asc(b$+chr$(0)) 60 : 70 rem fuer kassettenbetrieb: 80 rem zeile 20: open1,1,0,a$
290 open 1,1,0,na$:gosub 120:if x=2049 then 310 730 open 1,1,0,na$:gosub 120 999 rem die zeilen 1290 bis 1310 entfallen