Steve's multiple cross-reference solution will help you keep track of symbols and their uses in your assembly language programs.
October 01, 1988
URL:http://www.drdobbs.com/tools/a-double-cross-for-masm/184408011
Steve Heller has been programming for more than 18 years and is the president of Chrysalis Software Corp., P.O. Box 0335, Baldwin, NY 11510 He can be reached through CompuServe: 71101,1702.
Last year, while working for a small software house, I had the unenviable chore of working on a large assembler program that had mostly been written by a departed programmer and that was being modified by several people at the same time. The project consists of more than 40 files and approximately 1 Mbyte of source. Keeping track of the various changes was going to be difficult at best and presented the distinct possibility of unrestrained chaos.
One of the countermeasures I took was to write, in my own time, a utility to provide cross-references to all public symbols and their uses among the many source files that made up the program. I decided to spend my own time on this project, for two reasons. First, the company was reluctant to pay me to work on it, and second, by developing the utility in my own time, I'd eventually be able to share it with others.
After completing the utility, which have named Xxref (cross-cross reference) to distinguish it from single-file utilities such as the one included with MASM, I decided that writing this article was the best way to make it available to those who could use it the most.
The basic method for producing the cross-reference listing is as follows:
Because I wanted the utility working as soon as possible, I opted to use a high-level language (Turbo Pascal). For reference in the following discussion, refer to Listing One, which starts on page 87.
As mentioned, the program starts by initializing the various data structures (lines 614 - 699). First, it marks the heap so that it can free all the dynamically allocated storage with one statement at the end of the program. It then allocates storage for the Public Variable Array (PublicVarArray) and the Reference Pointer Array (RefPtrs).
If the user hasn't specified all the parameters on the command line, the program has to prompt for the missing ones. The first parameter is the name of the input file (which contains the names of the files to be processed), the second parameter is the name of the output file (to which the report will be written), and the third tells Xxref whether the user wants brief output (no page headers). The default is to print page headers.
Now the program can read in the names of the source files to be processed from the input file. Because I wanted to be able to accept a file produced by redirecting the output of the DIR command to a file, lines that are null, or that start with a blank or a period, or that refer to a file of zero bytes, are ignored. Otherwise, the program combines the file name and extension into one string. If the extension is null, it adds a . (period) to the name. Then it allocates storage for the file name and stores a pointer to it in the FileName array.
When Xxref has read in all the file names, it closes the input file, and sets the FileCount variable to the number of names it has read in. Because it hasn't yet seen any public symbols, it initializes the PublicCount variable to 0. Then it initializes the pointers in the PublicVarArr array--which will point to the public symbols as it encounters them--to the nil pointer.
The job of Pass1 (lines 703 - 755) is simply to read through the files and find all the public symbols. For each file that was named in the file-name input file, it reads in each line of the file, converts it to all uppercase, changes all tabs to blanks, and removes the first token from the line. A token is something that might be an identifier because it is made up of characters that are legal in identifiers, as specified in the constant LegalChars. The first token in the line must be PUBLIC for the program to process it on this first pass, because it is just looking for all the public symbols at this point.
If Pass1 finds the token PUBLIC at the beginning of the line, it removes all the rest of the tokens, one at a time, and sees whether pointers to them are already stored in the PublicVarArr array. If not, it adds them to that array and increments the number of PUBLICs that it has found. The procedure continues extracting tokens until there is nothing left in the line. This process is continued for each line in all the files.
In the second pass (lines 759 - 828) the program reads through all the files again, this time to find all the references. Following the second pass, it compacts the results for sorting (lines 832 - 860).
Pass2, the second pass, starts by initializing the RefPtrs array, which is an array of records that keeps track of the first and last references to each public symbol. It sets the First element of each of these records to 0, to indicate that it hasn't yet seen any symbol references. It also initializes the RefCount variable, which is used to index into the RefArrs array. RefCount keeps track of the number of references that the program has seen so far.
Next, for each file that was specified in the file-name input file, Pass2 reads each line of the file and searches it for public symbols.Whenever it finds one, it increments RefCount.
You will notice that the RefArrs array is allocated in pieces rather than all at once; if RefCount is 1 more than a multiple of RefSize, then the program has to allocate another block of the RefArrs array. One reason for this is that no one dynamically allocated variable can be more than 64K bytes long, and the RefArrs records are 6 bytes long. Thus, one array of such records must contain fewer than 11,000 records. For Xxref to be able to handle 65,000 references, it must allocate the array in sections. This strategy also allows a smaller program to be analyzed with less memory usage.
After the allocation of a new section, if necessary, Pass2 tests to see whether it has already seen a reference for the public symbol, by looking at the First field for the symbol. if that is zero, this is the first reference, so it sets First to the current RefCount. Otherwise, it uses the Last field to access the last reference on the chain and calculates the block number and item number within the block where the last reference is stored and updates its Next field to the number of the new reference it is constructing.
In either case, the procedure now sets the Last field to the number of the new reference it is constructing. Then it fills in the FileNum, LineNum, Next, and Define fields with, respectively, the number of the file in which it encountered the reference, the line number of the reference, zero (meaning there is no next reference to this symbol yet), and FALSE, which indicates that this is a reference and not a definition. This last is used to indicate, on the output, where the symbol was defined. Then, if it is at the beginning of a line, Pass2 must check whether this is, in fact, a definition. If it is, the value of LookAhead will be TRUE, which will result in setting the Define field to TRUE.
Next, Pass2 computes the block number and offset within the block where the new reference it has just constructed will be stored and copies the temporary record to its permanent location in that block.
It can now set the StartOfLine flag to FALSE, as it has processed the first token on the line. It removes the token and continues processing the same line. When no more tokens are available in the current line, it reads another line from the file. When no more lines are available from the current file, Pass2 closes the file.
If there is another file to be read, Pass2 opens it and processes it, as described earlier. If there are no more files, the routine finishes.
In order to speed up the search for symbols during pass 2, the program stored them in a hash table (see the routines Locate [lines 276 - 295], Store [lines 428 - 444], and Hashit [lines 252 - 271]). To sort the symbols by name, however, it must compact the table; otherwise, it will waste a lot of time sorting unused table entries. Therefore, CompactResults steps through thePublicVarArr, PublicFileArr, and RefPtrs arrays and moves all the entries that contain data to the front of the arrays, clearing out the old entries to prevent duplication.
The sort procedure Megasort (lines 159 - 219) is a distribution sort that I've described in detail in another article1, so I will not describe its operation here. For the principles of operation, you can refer either to my article or to Donald Knuth's classic The Art of Computer Programming.2 In short, the merits of a distribution sort are these: It's fast (for small keys), it's easy to implement, and the performance isn't data dependent.
The ailments to the sort procedure are, in order, the array of pointers to the keys to be sorted (the symbol names), an array to be rearranged along with the key pointers (the file numbers), another array to be rearranged along with the key pointers (the reference pointers), the number of bytes of the keys that are to be considered significant, and the number of keys to be sorted. upon return from the sort, the key pointers are reordered by the sorted values of the keys, and the other two arrays are reordered along with the keys that they are associated with.
The WriteResults procedure (lines 864 - 936), generates output either in print format, with headers and page numbers, or in screen format, with only one header at the top, as specified by the user.
Both brief output (no headers or footers and no page breaks) and listing output (headers, footers and page breaks) are supported by the WriteResults routine. The brief mode is preferable if the cross-reference is to be used on-line in an editor as headers on each page are unnecessary in such a use. The listing mode is better for printed output as it is harder to scroll back and forth in a printed listing and compactness is not as Important. For an example of the listing output, see Example 1 page 50. The brief output is basically the same, except that the headers are not repeated and no page breaks are inserted in the output.
Cross reference as of 3/13/1988 11:55:00 among files ADJMENU.ASM CMDS.ASM COLOR.ASM DATASEG.ASM DISKIO.ASM DRAW.ASM END.ASM ERROR.ASM FGLOBAL.ASM FL.ASM FLASH.ASM FLASHUP.ASM FLNBOX.ASM FLNOTES.ASM FMACRO.ASM FUNCTION.ASM FWCLIP.ASM FWED.ASM FWLINK.ASM FWMENU.ASM FWSUBS.ASM GETITEM.ASM GLOBAL.INC HARD.ASM HORIZ.ASM INTS.ASM KEYIO.INC LOADER.ASM MEGAFU.ASM MENU.ASM MENUDATA.INC MOUSECTL.ASM MOUSEKEY.ASM MOVE.ASM MVERSION.INC OVERLAP.ASP READBOX.ASM RETSTR.ASM SAL.ASM SCAN.ASM SCRMACS.INC SCRPROCS.ASM SEARCH.ASM SHOWHELP.ASM SUBS.ASM TP.ASM VIDEO.ASM Public symbol Declared in file ------------- ---------------- @FPRINT_STR .................(Continued) FWMEN.ASM 43 FWSUBS.ASM 55 372 731 869 1622 @FPRINT_STR_NOCOLOR ..........FL.ASM DRAW.ASM 22 FL.ASM 23* 30 FWMENU.ASM 44 614 FWSUBS.ASM 56 1397 @FPUT_CHAR .................. FL.ASM COLOR.ASM 11 218 221 233 235 DRAW.ASM 22 155 FL.ASM 67* 75 FWED.ASM 589 612 675 FWMENU.ASM 44 606 659 668 684 688 696 705 FWSUBS.ASM 56 1125 HORIZ.ASM 75 370 375 377 384 385 392 395 402 408 @FPUT_COLOR ..................FL.ASM COLOR.ASM 14 82 94 109 184 DRAW.ASM 21 FL.ASM 133* 155 FWED.ASM 1955 2305 FWSUBS.ASM 55 HORIZ.ASM 75 282 286 @FPUT_CX_CHARS ............... FL.ASM DRAW.ASM 21 FL.ASM 71 102* 129 FWED.ASM 2456 2474 2577 (* = line where symbol is defined)
In order to provide headers and footers on each page of the output, the program must keep track of the number of lines that have been printed. For simplicity in the main output routine, this chore has been relegated to the WriteOut routines (WriteOutCr, WriteOut, WriteOutLn, and WriteOutSym), which replace the normal Write and WriteLn routines. Each of these WriteOut routines includes the check for end of page and calls the Header routine to take care of the printing of the footer and header, if needed. The Header routine can also handle the breaking of a listing of one symbol over a page boundary by adding (Continued) to the listing on the new page.
After the initial header has been printed, which occurs even if the output is being done in brief mode, WriteResults starts processing all symbol references.
For each symbol, the first line of output includes its name, a line of periods, and the name of the file in which it was defined. If no references to that symbol were found, then a blank line follows. Otherwise, the routine prints the name of each file in which a reference was found, followed by the line numbers of all references a maximum of eight per line. Any line on which the symbol is defined will have an (asterisk) next to the line number. Whenever a reference comes from a different file from that of the last reference, the routine starts a new line. When all references to a symbol have been printed, it steps to the next symbol after printing two blank lines.
When all references to all symbols have been printed, WriteResults prints the total number of public symbols and the total number of references.
If you have large MASM programs to maintain, this utility should be useful. The source code for the original Turbo Pascal version described in this article is available from DDJ in the usual ways (diskette or CompuServe). Running times for the approximately 1-Mbyte program mentioned at the beginning of the article, which contained 1,070 public symbols with 9,290 references, were 560 seconds for the Turbo Pascal version and 160 seconds for the assembly-language version. (Both trials were timed on a 10-MHz, no-wait-state, 80286 AT clone.) This is a shareware program. if you find it useful you should register it by sending a check for $39.95, payable to Chrysalis Software Corp., to the address at the beginning of this article. Registered users will receive a copy of an assembly-enhanced version of the program.
Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.
[LISTING ONE]
_A DOUBLE CROSS FOR MASM_
by
Steven Heller
1: PROGRAM xxref; {Copyright 1987 by Chrysalis Software Corp. All rights reserved.}
2: {Last updated 871101 :1320}
3: {$R+}
4: {$I-}
5:
6: CONST
7: PubSize = 10000;
8: RefSize = 4096;
9: Refshift = 12;
10: LegalChars : Set of CHAR = ['A'..'Z','0'..'9','@','_'];
11: Blanks : String[33] = ' ';
12:
13: TYPE
14: AnyString = String[255];
15: SomeString = String[32];
16: StrPtrArr = ARRAY [1..PubSize] OF ^SomeString;
17: SortArray = ARRAY [Char] OF Integer;
18: NameArray = ARRAY [1..255] OF ^SomeString;
19: FileNumArray = ARRAY [1..PubSize] OF Byte;
20: RefPtr = Record
21: First: Integer;
22: Last : Integer;
23: End;
24: Ref = Record
25: Filenum : Byte;
26: Define : Boolean;
27: Linenum : Integer;
28: Next : Integer;
29: End;
30:
31: RefPtrarr = ARRAY [1..Pubsize] OF RefPtr;
32: RefArr = ARRAY [1..Refsize] OF Ref;
33:
34:
35:
36: VAR
37: PublicVarArr : ^StrPtrArr;
38: PublicFileArr : FileNumArray;
39: junk : AnyString;
40: line : AnyString;
41: temp : SomeString;
42: i,j,k,l,m,n : Integer;
43: RANum : Integer;
44: RAOff : Integer;
45: PublicCount,ExtrnCount : Integer;
46: infile : text[10000];
47: infilename : AnyString;
48: outfile : text[10000];
49: outfilename : AnyString;
50: KeyLen : Integer;
51: ArrayLength : Integer;
52: Filename : NameArray;
53: Filecount : Integer;
54: name:anystring;
55: ext:somestring;
56: possep: Integer;
57: Token : Somestring;
58: TempToken : Somestring;
59: TempRef : Ref;
60: StartOfLine : Boolean;
61: FoundDef : Integer;
62: OutLine : Integer;
63: LastSymLine : AnyString;
64: SameSym : Boolean;
65: SendCRs : Boolean;
66:
67: RefPtrs : ^RefPtrArr;
68: RefArrs : ARRAY [1..16] OF ^RefArr;
69: RefCount : Integer;
70: Page : Integer;
71: Brief : Boolean;
72:
73: Topofheap : ^Integer;
74:
75:
76: CONST
77: Separator = '.................................';
78:
79:
80:
81: PROCEDURE IOcheck(FileName : Somestring);
82:
83: VAR
84: IOCode : Integer;
85: Ch : Char;
86:
87: BEGIN
88:
89: IOCode := IOResult;
90: IF IOCode <> 0 THEN
91: BEGIN
92: WriteLn;
93: CASE IOCode OF
94: $01 : WriteLn('File ',FileName,' does not exist');
95: $02 : WriteLn('File ',FileName,' not open for input');
96: $03 : WriteLn('File ',FileName,' not open for output');
97: $04 : WriteLn('File ',FileName,' not open');
98: $05 : WriteLn('Can''t write to ',FileName);
99: $06 : WriteLn('Can''t read from ',FileName);
100: $f0 : WriteLn('Disk write error on ',FileName);
101: $f1 : WriteLn('Directory is full');
102: $ff : WriteLn('File ',FileName,' disappeared');
103: ELSE
104: WriteLn('Unknown I/O error on ',FileName);
105: END;
106: Halt;
107: END;
108: END;
109:
110:
111:
112:
113: FUNCTION GetDate:SomeString;
114:
115: CONST
116: Dos_get_date = $2a;
117: Dos_get_time = $2c;
118:
119: TYPE
120: regpack = RECORD
121: CASE integer OF
122: 1 : (ax,bx,cx,dx,bp,si,di,ds,es,flags:integer);
123: 2 : (al,ah,bl,bh,cl,ch,dl,dh:byte);
124: END;
125:
126: VAR
127: regs : regpack;
128: result : SomeString;
129: temp : SomeString;
130:
131: BEGIN
132: WITH regs DO
133: BEGIN
134: ah := dos_get_date;
135: msdos(regs);
136: Str(dh,result);
137: Str(dl,temp);
138: result := result + '/' + temp;
139: Str(cx,temp);
140: result := result + '/' + temp;
141: ah := dos_get_time;
142: msdos(regs);
143: Str(ch,temp);
144: IF length(temp) = 1 THEN temp := '0' + temp;
145: result := result + ' ' + temp;
146: Str(cl,temp);
147: IF length(temp) = 1 THEN temp := '0' + temp;
148: result := result + ':' + temp;
149: Str(dh,temp);
150: IF length(temp) = 1 THEN temp := '0' + temp;
151: result := result + ':' + temp;
152: END;
153:
154: GetDate := result;
155:
156: END;
157:
158:
159: PROCEDURE Megasort(VAR PtrArray:StrPtrArr; VAR SubArray1:FileNumArray;
160: VAR SubArray2:RefPtrArr;
161: KeyLength:Integer;ArraySize:Integer);
162:
163: VAR
164: l : Char;
165: m : Char;
166: i : Integer;
167: j : Integer;
168: BucketCount : SortArray;
169: BucketPosition : SortArray;
170: TempPtrArr : ^StrPtrArr;
171: TempSubArr1: FileNumArray;
172: TempSubArr2: ^RefPtrArr;
173:
174:
175: BEGIN
176:
177: New(TempPtrArr);
178: New(TempSubArr2);
179:
180: FOR i := KeyLength DOWNTO 1 DO
181: BEGIN
182: FOR l := #0 TO #255 DO
183: BucketCount[l] := 0;
184: FOR j := 1 TO ArraySize DO
185: BEGIN
186: IF i > length(PtrArray[j]^) THEN
187: m := #0
188: ELSE
189: m := PtrArray[j]^[i];
190: BucketCount[m] := BucketCount[m] + 1;
191: END;
192:
193: BucketPosition[#0] := 1;
194: FOR l := #1 TO #255 DO
195: BucketPosition[l] := BucketCount[pred(l)] + BucketPosition[pred(l)];
196:
197: FOR j := 1 TO ArraySize DO
198: BEGIN
199: IF i > length(PtrArray[j]^) THEN
200: m := #0
201: ELSE
202: m := PtrArray[j]^[i];
203: TempPtrArr^[BucketPosition[m]] := PtrArray[j];
204: TempSubArr1[BucketPosition[m]] := SubArray1[j];
205: TempSubArr2^[BucketPosition[m]] := SubArray2[j];
206: BucketPosition[m] := BucketPosition[m] + 1;
207: END;
208:
209: FOR j := 1 TO ArraySize DO
210: BEGIN
211: PtrArray[j] := TempPtrArr^[j];
212: SubArray1[j] := TempSubArr1[j];
213: SubArray2[j] := TempSubArr2^[j];
214: END;
215:
216: END;
217:
218:
219: END;
220:
221:
222:
223:
224:
225: PROCEDURE Canonical(VAR Old:Anystring); {external 'xxsubs.bin';}
226:
227: VAR
228: i : Integer;
229:
230: BEGIN
231:
232: IF old <> '' THEN
233: BEGIN
234: IF pos(';',old) > 0 THEN
235: old := copy(old,1,pos(';',old)-1);
236: IF old <> '' THEN
237: BEGIN
238: FOR i := 1 TO Length(old) DO
239: IF old[i] = chr(9) THEN
240: old[i] := ' '
241: ELSE IF old[i] IN ['a'..'z'] THEN
242: old[i] := Upcase(old[i]);
243: END;
244: END;
245:
246: END;
247:
248:
249:
250:
251:
252: FUNCTION Hashit(Pubs: Integer; VAR Actual:Somestring):Integer; {external Canonical[$52];}
253:
254: VAR
255: i : Integer;
256: temp : Integer;
257:
258: BEGIN
259:
260: temp := 0;
261:
262: IF Actual <> '' THEN
263: BEGIN
264: FOR i := 1 TO Length(Actual) DO
265: temp := ((temp shl 5) + (temp shr 11) + ord(Actual[i])) AND 32767;
266: Hashit := (temp MOD Pubs) + 1;
267: END
268: ELSE
269: Hashit := 0;
270:
271: END;
272:
273:
274:
275:
276: FUNCTION Locate(VAR StrArray:StrPtrArr;PublicSize:Integer;VAR Value:Somestring):Integer; {external Canonical[$86]
277:
278: VAR
279: i : Integer;
280:
281: BEGIN
282:
283: i := Hashit(Publicsize,Value);
284: WHILE (StrArray[i] <> nil) AND (StrArray[i]^ <> Value) DO
285: BEGIN
286: i := i + 1;
287: IF i > Publicsize THEN i := 1;
288: END;
289:
290: IF StrArray[i] = nil THEN
291: Locate := 0
292: ELSE
293: Locate := i;
294:
295: END;
296:
297:
298:
299:
300: PROCEDURE RemoveToken(VAR Line:Anystring; VAR Temp:Somestring); {external Canonical[$100];}
301:
302: VAR
303: i,j,k : Integer;
304: TempChar : Char;
305:
306: BEGIN
307:
308: Temp := '';
309:
310: IF Line <> '' THEN
311: BEGIN
312: i := 1;
313: WHILE (i <= length(line)) AND NOT(Line[i] IN LegalChars) DO
314: i := i + 1;
315: IF i <= length(line) THEN
316: BEGIN
317: j := i;
318: WHILE (j <= length(line)) AND (Line[j] IN LegalChars) DO
319: j := j + 1;
320: Temp := Copy(Line,i,j-i);
321: Line := Copy(Line,j,255);
322: END
323: ELSE
324: BEGIN
325: Temp := '';
326: Line := '';
327: END;
328: END
329: ELSE
330: BEGIN
331: Temp := '';
332: Line := '';
333: END;
334: END;
335:
336:
337:
338:
339: PROCEDURE NextToken(VAR Line:Anystring; VAR Temp:Somestring); {external Canonical[$1b2];}
340:
341: VAR
342: i,j,k : Integer;
343: TempChar : Char;
344:
345: BEGIN
346:
347: Temp := '';
348:
349: IF Line <> '' THEN
350: BEGIN
351: i := 1;
352: WHILE (i <= length(line)) AND NOT(Line[i] IN LegalChars) DO
353: i := i + 1;
354: IF i <= length(line) THEN
355: BEGIN
356: j := i;
357: WHILE (j <= length(line)) AND (Line[j] IN LegalChars) DO
358: j := j + 1;
359: Temp := Copy(Line,i,j-i);
360: END
361: ELSE
362: BEGIN
363: Temp := '';
364: END;
365: END
366: ELSE
367: BEGIN
368: Temp := '';
369: END;
370: END;
371:
372:
373:
374:
375: FUNCTION LookAhead(VAR Line:Anystring):Boolean;
376:
377: VAR
378: i : Integer;
379: Temp : Boolean;
380: Found : Boolean;
381:
382: BEGIN
383:
384: Temp := FALSE;
385: Found := FALSE;
386:
387: i := 1;
388: WHILE (i < length(line)) AND (line[i] = ' ') DO
389: i := i + 1;
390:
391: IF line[i] = ':' THEN
392: BEGIN
393: IF i = length(line) THEN
394: BEGIN
395: Temp := TRUE;
396: Found := TRUE;
397: END
398: ELSE {check the next char(s)}
399: BEGIN
400: REPEAT
401: i := i + 1;
402: UNTIL (i = length(line)) OR (line[i] <> ' ');
403: IF i = length(line) THEN {all trailing blanks}
404: BEGIN
405: Temp := TRUE;
406: Found := TRUE;
407: END;
408: END;
409: END;
410:
411: IF Found = FALSE THEN
412: IF copy(line,i,3) = 'END' THEN
413: BEGIN
414: Temp := FALSE;
415: Found := TRUE;
416: END;
417:
418: IF Found = FALSE THEN
419: Temp := (line[i] IN ['A'..'Z',':']);
420:
421: LookAhead := Temp;
422:
423: END;
424:
425:
426:
427:
428: FUNCTION Store(VAR StrArray:StrPtrArr;Value:Somestring):Integer;
429:
430: VAR
431: i : Integer;
432:
433: BEGIN
434:
435: i := Hashit(Pubsize,Value);
436: WHILE (StrArray[i] <> nil) DO
437: BEGIN
438: i := i + 1;
439: IF i > Pubsize THEN i := 1;
440: END;
441:
442: Store := i;
443:
444: END;
445:
446:
447:
448: PROCEDURE Header(Rewrite:Boolean);forward;
449:
450:
451:
452: PROCEDURE WriteOutCr;
453:
454: VAR
455: i : Integer;
456:
457: BEGIN
458:
459: IF SendCRs = TRUE THEN
460: BEGIN
461: WriteLn(outfile);
462: IOCheck(outfilename);
463:
464: IF Brief THEN exit;
465:
466: OutLine := OutLine + 1;
467: IF OutLine > 60 THEN
468: BEGIN
469: WriteLn(outfile);
470: IOCheck(outfilename);
471: WriteLn(outfile,' (* = line where symbol is defined)');
472: IOCheck(outfilename);
473: Write(outfile,chr(12));
474: IOCheck(outfilename);
475: OutLine := 0;
476: Header(SameSym);
477: END;
478: END;
479: END;
480:
481:
482:
483:
484: PROCEDURE WriteOut(Line:Anystring);
485:
486: VAR
487: i : Integer;
488:
489: BEGIN
490: Write(outfile,Line);
491: IOCheck(outfilename);
492: SendCRs := TRUE;
493: END;
494:
495:
496:
497:
498: PROCEDURE WriteOutLn(Line:Anystring);
499:
500: VAR
501: i : Integer;
502:
503: BEGIN
504:
505: SendCRs := TRUE;
506: WriteLn(outfile,Line);
507: IOCheck(outfilename);
508:
509: IF Brief THEN exit;
510:
511: OutLine := OutLine + 1;
512: IF OutLine > 60 THEN
513: BEGIN
514: WriteLn(outfile);
515: IOCheck(outfilename);
516: WriteLn(outfile,' (* = line where symbol is defined)');
517: IOCheck(outfilename);
518: Write(outfile,chr(12));
519: IOCheck(outfilename);
520: OutLine := 0;
521: Header(SameSym);
522: END;
523: END;
524:
525:
526:
527:
528: PROCEDURE WriteOutSym(Line:Anystring);
529:
530: VAR
531: i : Integer;
532:
533: BEGIN
534:
535: IF Brief THEN
536: BEGIN
537: WriteLn(outfile,Line);
538: IOCheck(outfilename);
539: SendCRs := TRUE;
540: exit;
541: END;
542:
543: IF OutLine > 58 THEN
544: BEGIN
545: FOR i := OutLine + 1 TO 61 DO
546: WriteLn(outfile);
547: WriteLn(outfile);
548: IOCheck(outfilename);
549: WriteLn(outfile,' (* = line where symbol is defined)');
550: IOCheck(outfilename);
551: Write(outfile,chr(12));
552: IOCheck(outfilename);
553: OutLine := 0;
554: Header(FALSE);
555: END;
556: LastSymLine := Line;
557: WriteLn(outfile,Line);
558: IOCheck(outfilename);
559: SendCRs := TRUE;
560: OutLine := OutLine + 1;
561: END;
562:
563:
564:
565:
566: PROCEDURE Header;
567:
568: VAR
569: i : Integer;
570: Temp : Anystring;
571:
572: BEGIN
573:
574: OutLine := 0;
575:
576: Str(Page,Temp);
577: Page := Page + 1;
578:
579: WriteOutCr;
580: WriteOutCr;
581: WriteOutLn(' XXREF V1.0 - Copyright 1987 by Chrysalis Software Corp. Page '+Temp);
582: WriteOutCr;
583: WriteOutLn(' Cross reference as of '+GetDate+' among files: ');
584: WriteOutCr;
585:
586: FOR i := 1 TO FileCount DO
587: BEGIN
588: WriteOut(copy(blanks,1,13-length(FileName[i]^))+FileName[i]^);
589: IF i MOD 6 = 0 THEN
590: WriteOutCr;
591: END;
592: IF FileCount MOD 6 <> 0 THEN
593: WriteOutCr;
594: WriteOutCr;
595: WriteOutCr;
596:
597: WriteOutLn('Public symbol Declared in file');
598: WriteOutLn('------------- ----------------');
599: WriteOutCr;
600:
601: IF Rewrite THEN
602: BEGIN
603: LastSymLine := copy(LastSymLine,1,33)+'(Continued)';
604: WriteOutLn(LastSymLine);
605: WriteOutCr;
606: END;
607:
608: SendCRs := FALSE;
609:
610: END;
611:
612:
613:
614: PROCEDURE Initialize;
615:
616: BEGIN
617:
618: Mark(Topofheap);
619:
620: New(PublicVarArr);
621: New(RefPtrs);
622:
623: IF ParamCount = 0 THEN
624: BEGIN
625: WriteLn('The parameters are (in this order):');
626: WriteLn('Input file name (which contains the names of the files to process)');
627: WriteLn('Output file name, where the report will be written');
628: WriteLn('Optional switch: -b (produce brief output, no page headers)');
629: Halt;
630: END;
631:
632: IF ParamCount = 1 THEN
633: BEGIN
634: infilename := ParamStr(1);
635: Write('Output file name: ');
636: ReadLn(outfilename);
637: Write('Brief output? (Y/N): ');
638: ReadLn(Temp);
639: Temp := UpCase(Temp);
640: Brief := Temp = 'Y';
641: END
642: ELSE IF ParamCount = 2 THEN
643: BEGIN
644: infilename := ParamStr(1);
645: outfilename := ParamStr(2);
646: Brief := FALSE;
647: END
648: ELSE IF ParamCount = 3 THEN
649: BEGIN
650: infilename := ParamStr(1);
651: outfilename := ParamStr(2);
652: Temp := ParamStr(3);
653: Temp := UpCase(Temp[2]);
654: Brief := Temp = 'B';
655: END;
656:
657: Assign(infile,infilename);
658: IOCheck(infilename);
659: Reset(infile);
660: IOCheck(infilename);
661: Assign(outfile,outfilename);
662: IOCheck(outfilename);
663: Rewrite(outfile);
664: IOCheck(outfilename);
665:
666: WriteLn;
667: WriteLn('Reading file names.');
668:
669: i := 0;
670: WHILE NOT EOF(infile) DO
671: BEGIN
672: readln(infile,line);
673: IOCheck(infilename);
674: IF (line <> '') AND (line[1] <> ' ') AND (line[1] <> '.')
675: AND (copy(line,13,9) <> ' 0') THEN
676: BEGIN
677: i := i + 1;
678: name:=copy(line,1,8);
679: ext:=copy(line,10,3);
680: if pos(' ',name)>0 then name:=copy(name,1,pos(' ',name)-1);
681: name := name+'.'+ext;
682: GetMem(FileName[i],length(name)+1);
683: Filename[i]^ := name;
684: END;
685: END;
686:
687: Close(infile);
688: IOCheck(infilename);
689:
690: Filecount := i;
691:
692: PublicCount := 0;
693:
694: FOR i := 1 TO Pubsize DO
695: BEGIN
696: PublicVarArr^[i] := nil;
697: END;
698:
699: END;
700:
701:
702:
703: PROCEDURE Pass1;
704:
705: BEGIN
706:
707: WriteLn;
708: WriteLn('Pass 1');
709: WriteLn;
710:
711: WriteLn('Reading files: ');
712: FOR i := 1 TO Filecount DO
713: BEGIN
714: Write(Filename[i]^:13);
715: IF i MOD 6 = 0 THEN WriteLn;
716: Assign(infile,Filename[i]^);
717: IOCheck(FileName[i]^);
718: Reset(infile);
719: IOCheck(FileName[i]^);
720: WHILE NOT EOF(infile) DO
721: BEGIN
722: ReadLn(infile,junk);
723: IOCheck(FileName[i]^);
724: Canonical(junk);
725: NextToken(junk,temp);
726: IF temp = 'PUBLIC' THEN
727: BEGIN
728: RemoveToken(junk,temp); {get rid of "PUBLIC"};
729: REPEAT
730: RemoveToken(junk,temp);
731: IF temp <> '' THEN
732: BEGIN
733: j := Locate(PublicVarArr^,Pubsize,temp);
734: IF j = 0 THEN
735: BEGIN
736: j := Store(PublicVarArr^,temp);
737: GetMem(PublicVarArr^[j],length(temp)+1);
738: PublicVarArr^[j]^ := temp;
739: PublicFileArr[j] := i;
740: PublicCount := PublicCount + 1;
741: END
742: ELSE
743: j := j; {for debugging}
744: END;
745: UNTIL junk = '';
746: END;
747: END;
748: Close(infile);
749: IOCheck(FileName[i]^);
750: END;
751:
752: WriteLn;
753: WriteLn;
754:
755: END;
756:
757:
758:
759: PROCEDURE Pass2;
760:
761: BEGIN
762:
763: WriteLn('Pass 2');
764: WriteLn;
765:
766: FOR i := 1 TO Pubsize DO
767: RefPtrs^[i].First := 0;
768:
769: RefCount := 0;
770:
771: WriteLn('Reading files: ');
772: FOR i := 1 TO Filecount DO
773: BEGIN
774: k := 0;
775: Write(Filename[i]^:13);
776: IF i MOD 6 = 0 THEN WriteLn;
777: Assign(infile,Filename[i]^);
778: IOCheck(FileName[i]^);
779: Reset(infile);
780: IOCheck(FileName[i]^);
781: WHILE NOT EOF(infile) DO
782: BEGIN
783: ReadLn(infile,junk);
784: IOCheck(FileName[i]^);
785: k := k + 1;
786: StartOfLine := TRUE; {we are at the beginning of the line}
787: Canonical(junk);
788: RemoveToken(junk,token);
789: IF token <> 'PUBLIC' THEN
790: BEGIN
791: WHILE token <> '' DO
792: BEGIN
793: j := Locate(PublicVarArr^,Pubsize,Token);
794: IF j <> 0 THEN
795: BEGIN
796: RefCount := RefCount + 1;
797: IF RefCount MOD Refsize = 1 THEN
798: New(RefArrs[RefCount DIV RefSize+1]);
799: IF RefPtrs^[j].First = 0 THEN
800: RefPtrs^[j].First := RefCount
801: ELSE
802: BEGIN
803: l := RefPtrs^[j].Last;
804: RANum := ((l-1) Shr Refshift) + 1;
805: RAOff := ((l-1) AND (Refsize-1)) + 1;
806: RefArrs[RANum]^[RAOff].Next := RefCount;
807: END;
808: RefPtrs^[j].Last := RefCount;
809: TempRef.FileNum := i;
810: TempRef.LineNum := k;
811: TempRef.Next := 0;
812: TempRef.Define := FALSE;
813: IF StartOfLine THEN {if at start of line, check for def}
814: TempRef.Define := LookAhead(junk);
815: RANum := ((RefCount-1) Shr Refshift) + 1;
816: RAOff := ((RefCount-1) AND (Refsize-1)) + 1;
817: RefArrs[RANum]^[RAOff] := TempRef;
818: END;
819: StartOfLine := FALSE;
820: RemoveToken(junk,token);
821: END;
822: END;
823: END;
824: Close(infile);
825: IOCheck(FileName[i]^);
826: END;
827:
828: END;
829:
830:
831:
832: PROCEDURE CompactResults;
833:
834: BEGIN
835:
836: j := 1;
837: k := 1;
838: FOR i := 1 TO Pubsize DO
839: BEGIN
840: IF PublicVarArr^[i] <> nil THEN
841: BEGIN
842: PublicVarArr^[j] := PublicVarArr^[i];
843: PublicFileArr[j] := PublicFileArr[i];
844: RefPtrs^[j] := RefPtrs^[i];
845: IF i > j THEN
846: BEGIN
847: PublicVarArr^[i] := nil;
848: PublicFileArr[i] := 0;
849: Refptrs^[i].First := 0;
850: RefPtrs^[i].Last := 0;
851: END;
852: j := j + 1;
853: END;
854: END;
855:
856:
857: WriteLn;
858: WriteLn;
859:
860: END;
861:
862:
863:
864: PROCEDURE WriteResults;
865:
866: BEGIN
867:
868: WriteLn('Writing output file.');
869:
870: Page := 1;
871: LastSymLine := '';
872: SendCRs := TRUE;
873: Header(FALSE);
874:
875: FOR i := 1 TO PublicCount DO
876: BEGIN
877: WriteOutCr;
878: WriteOutSym(PublicVarArr^[i]^+copy(separator,1,33-length (PublicVarArr^[i]^))+FileName[PublicFileArr[i]]^);
879: SameSym := TRUE;
880: IF RefPtrs^[i].First = 0 THEN
881: WriteOutCr
882: ELSE
883: BEGIN
884:
885: k := 0;
886: l := 0;
887: j := RefPtrs^[i].First;
888: REPEAT
889:
890: RANum := ((j-1) Shr Refshift) + 1;
891: RAOff := ((j-1) AND (Refsize-1)) + 1;
892: m := RefArrs[RANum]^[RAOff].Filenum;
893: junk := FileName[RefArrs[RANum]^[RAOff].Filenum]^;
894:
895: IF k = m THEN
896: BEGIN
897: IF (l >= 8) THEN
898: BEGIN
899: WriteOutCr;
900: WriteOut(copy(blanks,1,18));
901: l := 0;
902: END;
903: END
904: ELSE
905: BEGIN
906: l := 0;
907: WriteOutCr;
908: WriteOut(' ');
909: WriteOut(junk+copy(blanks,1,13-length(junk)));
910: END;
911:
912: Str(RefArrs[RANum]^[RAOff].Linenum,Temp);
913: Temp := copy(blanks,1,6-length(temp))+temp;
914: WriteOut(temp);
915: IF RefArrs[RANum]^[RAOff].Define THEN
916: WriteOut('*')
917: ELSE
918: WriteOut(' ');
919: l := l + 1;
920: k := m;
921: j := RefArrs[RANum]^[RAOff].Next;
922: UNTIL j = 0;
923: SameSym := FALSE;
924: WriteOutCr;
925: WriteOutCr;
926: END;
927: END;
928:
929: WriteOutCr;
930: Str(PublicCount,Temp);
931: WriteOutLn('Number of publics: '+Temp);
932: Str(Refcount,Temp);
933: WriteOutLn('Number of references: '+Temp);
934: WriteOut(chr(12));
935:
936: END;
937:
938:
939:
940: PROCEDURE Terminate;
941:
942: BEGIN
943:
944: Close(infile);
945: IOCheck(infilename);
946: Close(outfile);
947: IOCheck(outfilename);
948:
949: WriteLn('Done.');
950: Release(Topofheap);
951:
952: END;
953:
954:
955:
956: BEGIN
957:
958: Initialize;
959:
960: Pass1;
961:
962: Pass2;
963:
964: CompactResults;
965:
966: WriteLn('Sorting');
967:
968: Megasort(PublicVarArr^,PublicFileArr,RefPtrs^,32,PublicCount);
969:
970: WriteResults;
971:
972: Terminate;
973:
974: END.
975:
976:
Example 1.
XXREF V1.0 - Copyright 1987 by Chrysalis Software Corp. Page 5
Cross reference as of 3/13/1988 11:55:00 among files:
ADJMENU.ASM CMDS.ASM COLOR.ASM DATASEG.INC DISKIO.ASM DRAW.ASM
END.ASM ERROR.ASM FGLOBAL.ASM FL.ASM FLASH.ASM FLASHUP.ASM
FLNBOX.ASM FLNOTES.ASM FMACRO.ASM FUNCTION.ASM FWCLIP.ASM FWED.ASM
FWLINK.ASM FWMENU.ASM FWSUBS.ASM GETITEM.ASM GLOBAL.INC HARD.ASM
HORIZ.ASM INTS.ASM KEYIO.INC LOADER.ASM MEGAFU.ASM MENU.ASM
MENUDATA.INC MOUSECTL.ASM MOUSEKEY.ASM MOVE.ASM MVERSION.INC OVERLAP.ASM
READBOX.ASM RETSTR.ASM SAL.ASM SCAN.ASM SCRMACS.INC SCRPROCS.ASM
SEARCH.ASM SHOWHELP.ASM SUBS.ASM TP.ASM VIDEO.ASM
Public symbol Declared in file
------------- ----------------
@FPRINT_STR......................(Continued)
FWMENU.ASM 43
FWSUBS.ASM 55 372 731 869 886 1622
@FPRINT_STR_NOCOLOR..............FL.ASM
DRAW.ASM 22
FL.ASM 23* 30
FWMENU.ASM 44 614
FWSUBS.ASM 56 1397
@FPUT_CHAR.......................FL.ASM
COLOR.ASM 11 218 221 233 235
DRAW.ASM 22 155
FL.ASM 67* 75
FWED.ASM 589 612 675
FWMENU.ASM 44 606 659 668 684 688 696 705
FWSUBS.ASM 56 1125
HORIZ.ASM 75 370 375 377 384 385 392 395
402 408
@FPUT_COLOR......................FL.ASM
COLOR.ASM 14 82 94 109 184
DRAW.ASM 21
FL.ASM 133* 155
FWED.ASM 1955 2305
FWMENU.ASM 43 517 532 749
FWSUBS.ASM 55
HORIZ.ASM 75 282 286
@FPUT_CX_CHARS...................FL.ASM
DRAW.ASM 21
FL.ASM 71 102* 129
FWED.ASM 2456 2474 2577
(* = line where symbol is defined)
XXREF.PAS page 17