_STRUCTURED PROGRAMMING COLUMN_ by Kent Porter [LISTING 1] 1| Program number; 2| 3| { Puts line numbers at start of each line, stores in file by same 4| name except extension is .NUM } 5| 6| USES crt; 7| 8| VAR Filename, Newname : STRING [80]; 9| I, O : TEXT; 10| Line : STRING [135]; 11| Nbr, len, p : WORD; 12| Num : STRING [4]; 13| 14| BEGIN 15| Nbr := 0; 16| Newname := ''; 17| IF ParamCount < 1 THEN BEGIN 18| Writeln ('USAGE: NUMBER '); 19| EXIT; 20| END; 21| Filename := ParamStr (1); 22| 23| Len := pos ('.', Filename); 24| IF len = 0 THEN 25| Newname := Filename + '.NUM' 26| ELSE BEGIN 27| FOR p := 1 TO len DO 28| Newname := Newname + Filename [p]; 29| Newname := Newname + 'NUM'; 30| END; 31| Assign (I, Filename); 32| {$I-} 33| Reset (I); 34| {$I+} 35| IF IOResult <> 0 THEN BEGIN 36| Writeln ('Unable to open ', Filename); 37| EXIT; 38| END; 39| Assign (O, Newname); 40| Rewrite (O); 41| Writeln; 42| 43| WHILE NOT eof (I) DO BEGIN 44| Readln (I, Line); 45| INC (Nbr); 46| GotoXY (1, WhereY-1); Writeln (Nbr); 47| Str (Nbr:4, Num); 48| Line := Num + '| ' + Line; 49| Writeln (O, Line); 50| END; 51| Close (O); 52| Close (I); 53| GotoXY (1, WhereY-1); Writeln (nbr, ' lines in file'); 54| Writeln ('Output is in ', Newname); 55| END. 56| 57| [LISTING 2] Case-sensitive symbolic cross-reference for number.pas 0 (3), 15, 24, 35 1 (7), 17, 21, 27, 46, 46, 53, 53 135 (1), 10 4 (2), 12, 47 80 (1), 8 Assign (2), 31, 39 Close (2), 51, 52 crt (1), 6 eof (1), 43 EXIT (2), 19, 37 Filename (7), 8, 21, 23, 25, 28, 31, 36 GotoXY (2), 46, 53 I (6), 9, 31, 33, 43, 44, 52 IOResult (1), 35 Len (1), 23 len (3), 11, 24, 27 Line (5), 10, 44, 48, 48, 49 Nbr (5), 11, 15, 45, 46, 47 nbr (1), 53 Newname (9), 8, 16, 25, 28, 28, 29, 29, 39, 54 NOT (1), 43 Num (3), 12, 47, 48 number (1), 1 O (5), 9, 39, 40, 49, 51 p (3), 11, 27, 28 ParamCount (1), 17 ParamStr (1), 21 pos (1), 23 Program (1), 1 Readln (1), 44 Reset (1), 33 Rewrite (1), 40 Str (1), 47 STRING (3), 8, 10, 12 TEXT (1), 9 USES (1), 6 WhereY (2), 46, 53 WORD (1), 11 Writeln (7), 18, 36, 41, 46, 49, 53, 54 -- 40 symbols reported [LISTING 3] 1| PROGRAM Xref; 2| 3| { Builds and lists a Pascal/Modula-2 symbol cross-reference report } 4| { Uses binary trees and doubly-linked lists to effect B-Tree } 5| { Command line is XREF [/C|/N] } 6| { /C makes xref case-sensitive } 7| { /N makes it non-case sensitive (default) } 8| { Turbo Pascal 5.0 (4.0 will work, too) } 9| { K. Porter, DDJ, December '88 Structured Programming Column } 10| 11| USES crt, printer; 12| 13| TYPE SymString = STRING [39]; 14| CharSet = SET OF CHAR; 15| LineString = STRING [135]; 16| XLinePtr = ^XLineNode; { Pointer to xref line number node } 17| XLineNode = RECORD { Xref line number structure (SLL) } 18| Line : WORD; 19| Next : XLinePtr; 20| END; 21| 22| SymTreePtr = ^SymTreeNode; { Pointer to symbol tree node } 23| SymTreeNode = RECORD { Binary tree symbol node } 24| Symbol : SymString; 25| UCsymbol : SymString; 26| Count : WORD; 27| XList : XLinePtr; 28| LLink, RLink : SymTreePtr; 29| END; 30| 31| CONST Quote = #39; 32| DQuote = #34; 33| Eject = #12; 34| SymChars : CharSet = ['0'..'9','A'..'Z','a'..'z','.','_','^']; 35| PComment : CharSet = ['{', '}', '(', '*', ')', Quote, DQuote]; 36| Heading = ' symbolic cross-reference for '; 37| 38| VAR Filepath : STRING [80]; 39| Case_Sensitive : BOOLEAN; 40| F : TEXT; 41| Head, Alpha : SymTreePtr; 42| CommentLevel : WORD; 43| Line : LineString; 44| LineNumber : WORD; 45| NSymbols : WORD; 46| { ------------------------------------------------------------------ } 47| 48| PROCEDURE FindEndOfComment (VAR line : LineString; 49| VAR i : WORD; 50| eoc : CHAR); 51| { Scan until end of current comment is found } 52| 53| VAR ch : CHAR; 54| Searching : BOOLEAN; 55| 56| BEGIN 57| Searching := TRUE; 58| WHILE Searching DO BEGIN 59| WHILE i <= Length (Line) DO BEGIN 60| ch := Line [i]; 61| INC (i); 62| IF ch = eoc THEN 63| CASE eoc OF 64| '}': Searching := FALSE; 65| '*': IF line [i] = ')' THEN BEGIN 66| Searching := FALSE; 67| INC (i); 68| END; 69| Quote: Searching := FALSE; 70| DQuote: Searching := FALSE; 71| END; 72| 73| IF Searching = FALSE THEN BEGIN 74| DEC (CommentLevel); 75| EXIT; 76| END; 77| END; 78| 79| { If we get here, the comment continues on the next line } 80| Readln (F, Line); 81| i := 1; 82| INC (LineNumber); 83| END; 84| END; 85| { --------------------------- } 86| 87| FUNCTION UpShift (VAR Symbol : SymString) : SymString; 88| { Return upshifted version of passed string } 89| 90| VAR p : INTEGER; 91| s : SymString; 92| 93| BEGIN 94| s := ''; 95| FOR p := 1 TO Length (Symbol) DO 96| s := s + UpCase (Symbol [p]); 97| UpShift := s; 98| END; 99| { --------------------------- } 100| 101| FUNCTION NewNode (VAR Symbol : SymString) : SymTreePtr; 102| { Allocate and set up new symbol node, return pointer } 103| 104| VAR node : SymTreePtr; 105| 106| BEGIN 107| NEW (node); 108| Node^.Symbol := Symbol; 109| Node^.UCSymbol := UpShift (Symbol); 110| Node^.Count := 1; 111| Node^.XList := NIL; 112| Node^.RLink := NIL; 113| Node^.LLink := NIL; 114| Node^.RLink := NIL; 115| NewNode := node; 116| END; 117| { --------------------------- } 118| 119| FUNCTION Token (VAR line : LineString; 120| VAR i : WORD) : SymString; 121| { Return next symbol or keyword from line } 122| { Index to next char returned as a side-effect } 123| { Also checks for comments } 124| 125| VAR sym : SymString; 126| ch, ScanChar : CHAR; 127| nch : WORD; 128| 129| BEGIN 130| { Scan for first valid alphanumeric or for comment } 131| ScanChar := #0; 132| WHILE (NOT (Line [i] IN SymChars)) AND (i <= Length (line)) DO BEGIN 133| ch := line [i]; 134| INC (i); 135| IF ch IN PComment THEN BEGIN 136| CASE ch OF 137| Quote: BEGIN 138| INC (CommentLevel); 139| ScanChar := Quote; 140| END; 141| '{': BEGIN 142| INC (CommentLevel); 143| ScanChar := '}'; 144| END; 145| '}': IF CommentLevel > 0 THEN 146| DEC (CommentLevel); 147| '(': IF line [i] = '*' THEN BEGIN 148| INC (CommentLevel); 149| ScanChar := '*'; 150| INC (i); 151| END; 152| '*': IF line [i] = ')' THEN 153| IF CommentLevel > 0 THEN BEGIN 154| DEC (CommentLevel); 155| INC (i); 156| END; 157| END; 158| IF CommentLevel > 0 THEN 159| FindEndOfComment (line, i, ScanChar); 160| END; 161| END; 162| 163| { Pull out the symbol } 164| sym := ''; 165| nch := 1; 166| IF i < Length (Line) THEN 167| REPEAT 168| ch := Line [i]; 169| IF ch IN SymChars THEN BEGIN 170| IF (ch = '^') AND (nch = 1) THEN 171| { Skip leading pointer char } 172| ELSE BEGIN 173| sym := sym + ch; 174| INC (nch); 175| END; 176| INC (i); 177| END; 178| UNTIL (NOT (ch IN SymChars)) OR (i > Length (Line)); 179| IF NOT Case_Sensitive THEN 180| Token := UpShift (sym) 181| ELSE 182| Token := sym; 183| END; 184| { --------------------------- } 185| 186| FUNCTION BNode (VAR sym : SymString) : SymTreePtr; 187| { Find sym's node in binary tree, or add it if it doesn't exist } 188| 189| VAR Node, Parent : SymTreePtr; 190| 191| BEGIN 192| Node := Head; 193| WHILE ((Node^.Symbol <> sym) AND (Node <> NIL)) DO BEGIN 194| Parent := Node; 195| IF sym < Node^.Symbol THEN 196| Node := Node^.LLink 197| ELSE 198| Node := Node^.RLink 199| END; 200| IF Node <> NIL THEN { Node exists for this symbol } 201| INC (Node^.Count) 202| ELSE BEGIN { Else add new node to binary tree } 203| Node := NewNode (sym); 204| IF sym < Parent^.Symbol THEN { Update parent's pointer } 205| Parent^.LLink := Node 206| ELSE 207| Parent^.RLink := Node 208| END; 209| BNode := Node; 210| END; 211| { --------------------------- } 212| 213| PROCEDURE Append (Target : SymTreePtr; LineNbr : WORD); 214| { Add line cross-ref to target's dependent list } 215| 216| VAR XR, Parent : XLinePtr; 217| 218| BEGIN 219| IF Target^.XList = NIL THEN BEGIN { First occurrence of symbol } 220| NEW (XR); 221| XR^.Line := LineNbr; 222| XR^.Next := NIL; 223| Target^.XList := XR; 224| END 225| ELSE BEGIN { Append to end of existing list } 226| XR := Target^.Xlist; 227| REPEAT 228| Parent := XR; 229| XR := XR^.Next 230| UNTIL XR = NIL; { Find list's tail } 231| NEW (XR); { Append there } 232| XR^.Line := LineNbr; 233| XR^.Next := NIL; 234| Parent^.Next := XR; 235| END; 236| END; 237| { --------------------------- } 238| 239| PROCEDURE AddToTree (VAR Symbol : SymString; LineNbr : WORD); 240| { Place symbol into binary tree, add line xref to dependent list } 241| 242| VAR Target : SymTreePtr; 243| 244| BEGIN 245| IF Head = NIL THEN BEGIN { The tree is empty, so start it } 246| Head := NewNode (Symbol); { Build first binary node } 247| Append (Head, LineNbr); { Build first XREF node } 248| END 249| ELSE BEGIN 250| Target := BNode (Symbol); 251| Append (Target, LineNbr); 252| END; 253| END; 254| { --------------------------- } 255| 256| PROCEDURE Process (VAR Line : LineString); 257| { Controls parsing and construction of BTree } 258| 259| VAR Symbol : SymString; 260| p, oldp : WORD; 261| 262| BEGIN 263| p := 1; 264| IF Length (Line) > 0 THEN 265| WHILE p <= Length (Line) DO BEGIN 266| oldp := p; 267| Symbol := Token (line, p); { Get symbol } 268| IF Symbol = 'BEGIN' THEN Symbol := '' { Weed out nuisances } 269| ELSE IF Symbol = 'END' THEN Symbol := '' 270| ELSE IF Symbol = 'IF' THEN Symbol := '' 271| ELSE IF Symbol = 'THEN' THEN Symbol := '' 272| ELSE IF Symbol = 'ELSE' THEN Symbol := '' 273| ELSE IF Symbol = 'DO' THEN Symbol := '' 274| ELSE IF Symbol = 'WHILE' THEN Symbol := '' 275| ELSE IF Symbol = 'FOR' THEN Symbol := '' 276| ELSE IF Symbol = 'TO' THEN Symbol := '' 277| ELSE IF Symbol = 'VAR' THEN Symbol := '' 278| ELSE IF Symbol = 'INC' THEN Symbol := '' 279| ELSE IF Symbol = 'DEC' THEN Symbol := '' 280| ELSE IF Symbol = 'OF' THEN Symbol := '' 281| ELSE IF Symbol = 'PROGRAM' THEN Symbol := '' 282| ELSE IF Symbol = 'END.' THEN Symbol := ''; 283| IF Length (Symbol) > 0 THEN 284| AddToTree (Symbol, LineNumber); { Place info in BTree } 285| IF p = oldp THEN INC (p); { Prevents endless loop } 286| END; 287| END; 288| { --------------------------- } 289| 290| PROCEDURE Report (Node : SymTreePtr); 291| { Print symbol cross-reference listing } 292| { In-order (recursive) traversal of binary tree, printing the info 293| and dependent list for each node } 294| 295| VAR Col, Width : WORD; 296| Lnode : XLinePtr; 297| 298| PROCEDURE NewLine; 299| { Control pagination } 300| BEGIN 301| Writeln (LST); 302| Col := 0; 303| INC (LineNumber); 304| IF LineNumber > 58 THEN BEGIN 305| Write (LST, Eject); 306| Writeln (LST, 'Continuing cross-reference for ', Filepath); 307| Writeln (LST); 308| LineNumber := 2; 309| END; 310| END; { End nested procedure } 311| 312| BEGIN 313| IF node <> NIL THEN BEGIN 314| Report (Node^.LLink); { Follow left-hand path } 315| 316| { Print info from node } 317| Col := 0; 318| Write (LST, Node^.Symbol, ' (', Node^.Count, ')'); 319| Col := Col + Length (Node^.Symbol) + 6; 320| 321| { Print line number references } 322| Lnode := Node^.XList; 323| While Lnode <> NIL DO BEGIN 324| IF Col > 0 THEN 325| Write (LST, ', ', Lnode^.Line) 326| ELSE 327| Write (LST, ' ', Lnode^.Line); 328| IF Lnode^.Line < 10 THEN Width := 1 329| ELSE IF Lnode^.Line > 99 THEN Width := 3 330| ELSE Width := 2; 331| Col := Col + Width + 2; 332| IF (Col > 70) AND (Lnode^.Next <> NIL) THEN NewLine; 333| Lnode := Lnode^.Next; 334| END; 335| NewLine; 336| 337| Report (Node^.RLink); { Then follow right-hand path } 338| END; 339| END; 340| { --------------------------- } 341| 342| PROCEDURE Alphabetize (sym : SymTreePtr); 343| { Rearrange tree when case-sensitive so that upper- and lower-case 344| identifiers occur in alphabetic order regardless of case } 345| { RECURSIVE: Traverses symbol table in-order, builds alpha list } 346| 347| PROCEDURE Resort (sym : SymTreePtr); 348| { NESTED: Place new node in tree headed by Alpha pointer } 349| 350| VAR Node, Parent : SymTreePtr; 351| UCsymbol : SymString; 352| 353| BEGIN 354| IF Alpha = NIL THEN BEGIN { Make first node in sorted tree } 355| Alpha := NewNode (sym^.symbol); 356| Alpha^.count := sym^.count; 357| Alpha^.XList := sym^.XList; 358| END 359| ELSE BEGIN { Add new node in order } 360| UCsymbol := UpShift (sym^.symbol); 361| Node := Alpha; 362| WHILE node <> NIL DO BEGIN { Find insertion point } 363| Parent := node; 364| IF UCsymbol < Node^.UCsymbol THEN { based on U/C symbol } 365| Node := Parent^.LLink 366| ELSE 367| Node := Parent^.RLink; 368| END; 369| Node := NewNode (sym^.symbol); { Add node } 370| Node^.Count := sym^.count; 371| Node^.XList := sym^.XList; 372| IF UCsymbol < Parent^.UCsymbol THEN 373| Parent^.LLink := node 374| ELSE 375| Parent^.RLink := node; 376| END; 377| END; 378| 379| BEGIN { Body of Alphabetize } 380| IF sym <> NIL THEN BEGIN 381| Alphabetize (sym^.LLink); { Do nodes to left } 382| Resort (sym); { Realphabetize this node } 383| Alphabetize (sym^.RLink); { Now do nodes to right } 384| Dispose (sym); { All thru with this node } 385| END; 386| END; 387| { --------------------------- } 388| 389| PROCEDURE Count (Node : SymTreePtr); 390| { Count nodes in tree } 391| BEGIN 392| IF node <> NIL THEN BEGIN 393| Count (Node^.LLink); 394| INC (NSymbols); 395| Count (Node^.RLink); 396| END 397| END; 398| { --------------------------- } 399| 400| BEGIN 401| { Initialize } 402| Head := NIL; 403| Alpha := NIL; 404| CommentLevel := 0; 405| LineNumber := 0; 406| NSymbols := 0; 407| 408| { Process command line } 409| IF ParamCount < 1 THEN BEGIN 410| Writeln ('USAGE: XREF [/C|/N]'); 411| EXIT; 412| END; 413| Filepath := ParamStr (1); 414| IF pos ('.', Filepath) = 0 THEN 415| Filepath := Filepath + '.PAS'; { Default is Pascal file } 416| Case_Sensitive := FALSE; { Set default case sensitivity } 417| IF ParamCount = 2 THEN { or alter per command line } 418| IF (ParamStr (2) = '/c') OR (ParamStr (2) = '/C') THEN 419| Case_Sensitive := TRUE; 420| 421| { Open the file } 422| Assign (F, Filepath); 423| {$I-} 424| Reset (F); 425| {$I+} 426| IF IOResult <> 0 THEN BEGIN 427| Writeln ('Unable to open ', Filepath); 428| EXIT; 429| END; 430| 431| { Announce the program } 432| ClrScr; 433| IF Case_Sensitive THEN 434| Write ('Case-sensitive') 435| ELSE 436| Write ('Non-case sensitive'); 437| Writeln (Heading, Filepath); 438| Writeln; 439| 440| { Process the file } 441| WHILE NOT eof (F) DO BEGIN 442| Readln (F, line); 443| INC (LineNumber); 444| GotoXY (1, WhereY-1); Writeln (LineNumber); { Meter line number } 445| Process (Line); 446| END; 447| Close (F); 448| GotoXY (1, WhereY-1); Writeln (LineNumber, ' lines in file'); 449| IF CommentLevel <> 0 THEN 450| Writeln ('Unbalanced comments detected'); 451| 452| { Alphabetize tree into non-ASCII order if case-sensitive } 453| LineNumber := 3; 454| IF Case_Sensitive THEN BEGIN 455| Alphabetize (Head); 456| Writeln (LST, 'Case-sensitive', Heading, Filepath); 457| Writeln (LST); 458| Report (Alpha); 459| Count (Alpha); 460| END 461| ELSE BEGIN 462| Writeln (LST, 'Non-case sensitive', Heading, Filepath); 463| Writeln (LST); 464| Report (Head); 465| Count (Head); 466| END; 467| Writeln (LST); 468| Writeln (LST, '-- ', NSymbols, ' symbols reported'); 469| Write (LST, Eject); 470| END.