Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

A Double Cross for MASM


OCT88: A DOUBLE CROSS FOR MASM

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.

General Description

The basic method for producing the cross-reference listing is as follows:

    1. Initialize the data structures, initialize the data structures, including reading in the names of all the source files to be processed.

    2. Read through each file, looking for PUBLIC declarations and and storing the variable names and labels that are encountered in a symbol table.

    3. Read through each file again, looking for references to the public variables or labels encountered during the first pass and adding these references to the linked list of references for each PUBLIC symbol. When finished, compact the results for sorting.

    4. Sort the symbol table by symbol name.

    5. Write the 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.

Implementation

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.

Taking a First Pass

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.

One More Time

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.

CompactResults

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.

Sorting the Symbol Table

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.

Getting the Results

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.

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.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.

Conclusion

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.

Notes

    1. Steve Heller "MegaSort: A Distribution Sort," Computer Language (November 1987): 63-68.

    2. Donald E. Knuth. The Art of Computer Programming, Volume 3 (Reading, Mass.: Addison-Wesley, 1973.)

[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










Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.