{$U-} {$C-} { TYPEX.PAS Jim Mischel, June 1, 1986 Program listing and variable cross-reference generator for Turbo Pascal programs. Usage is TYPEX [] [;] Options are: I - INCLUDE files also X - Create program Cross-reference Defaults: Output - LST: Includes - NO Xref - NO If memory size is a consideration, INITIALIZE, PROCESS_FILE, and PRINT_XREF can be made overlay procedures, with a savings of approximately 2.5K bytes. This program evolved from LISTER.PAS that was included on the Turbo Pascal distribution disk. Some of the original code still exists. The procedure GETDATE may have to be changed for use with MS-DOS. It will NOT work with CP/M 2.2 without modification. It will work with MP/M, CP/M 3.x, and TurboDOS 1.3 or higher. This program was written using Turbo Pascal version 3.0 for CP/M. I have not tested it on any other operating system, though it should work except as noted above. MODIFICATIONS: 06/01/86 - jim - Initial coding. 10/21/86 - jim - Use a pointer-reversal in PRINT_REFS in place of the recurrsive list traversal. 11/30/86 - jim - Make the tree a right in-threaded tree. This speeds printing of the cross-reference. Add the FSTPTR field to the node record. References are now added in order of occurance. FSTPTR points to the first reference record, and NXTPTR points to the last. Also added NUMREFS to the record to prevent having to scan the list twice. PRINT_REFS is now a simple linked list traversal procedure. } program typex; const version_no = '2.5'; printwidth = 70; { print width for each line } printlength = 55; { # of lines to print on each page } pathlength = 14; { maximum length of file name } default_output = 'LST:'; { default destination } include_default = false; { default to no include files } xref_default = false; { default to no cross-reference } refs_per_line = 10; { max. number of references per line } max_id_len = 15; { max. id length for references on same line } optchr = ';'; { option seperator character } type filename = string[pathlength]; string8 = string[8]; string255 = string[255]; strptr = ^string255; refptr = ^reference; reference = record { item reference record } line, { source line of reference } incl : integer; { line in include file (if any) } nxtptr : refptr; { pointer to next reference } end; itmptr = ^item; item = record idname : strptr; { pointer to id name } left, { left node of binary tree } right : itmptr; { right node of binary tree } rthrd : boolean; { TRUE if right is thread pointer } fstptr, { pointer to first reference } nxtptr : refptr; { pointer to last reference } numrefs : integer; { Reference counter. This is NOT a count of references to this ID. It is used by PRINT_REFS to figure out how many lines it will take to print all the references for this item. } end; var page_no, { current page number } currow : integer; { current row in output file } outfile, { listing file } mainfile : text; { source file } mainfilename : filename; { input file name } search : array[1..4] of string[4]; { search strings for includes } date, { date returned from get_date } time : string8; { time returned from get_date } dots : string[70]; { line of dots for page header } xref, { TRUE = generate cross-reference } includes : boolean; { TRUE = process include files } xref_head : itmptr; { root of cross-reference tree } { PAGE - move output to new page } procedure page(var outfile : text); const ff = ^L; begin write(outfile,ff); end; { HEADINGS - move to new page and print headings. } procedure headings; begin page(outfile); page_no := page_no + 1; write(outfile,date:8); write(outfile,mainfilename:39); writeln(outfile,time:33); writeln(outfile,dots,'Page ',page_no:5); writeln(outfile); currow := 0; end; { headings } { OPEN - open file FP with name NAME. Return TRUE if operation successful. } function open(var fp : text; name : filename) : boolean; begin assign(fp,name); {$i- turn off I/O error checking} reset(fp); {$i+ error checking back on} if ioresult <> 0 then begin open := false; close(fp); end else open := true; end { open }; { INITIALIZE - set parameters and open files } procedure initialize; { GET_DATE - get date and time from system and convert to two strings. Date is stored as MM/DD/YY. Time is stored as HH:MM:SS, with seconds set to 00. This routine will not work for dates prior to 01/01/78 } procedure get_date(var date_ptr,time_ptr); type month_array = array[1..2,1..12] of integer; string8 = string[8]; var date : string8 absolute date_ptr; time : string8 absolute time_ptr; date_time : packed array [1..4] of char; jdate : integer absolute date_time; { #days since 12/31/77 } x, month : byte; year : integer; const day_table : month_array = ((31,59,90,120,151,181,212,243,273,304,334,365), (31,60,91,121,152,182,213,244,274,305,335,366)); { LEAP - return TRUE if YEAR is a leap year } function leap(year : integer) : boolean; begin leap := (year mod 4 = 0) and (year <> 100); end; {leap} { DAYS_IN - return number of days in YEAR } function days_in(year : integer) : integer; begin if (leap(year)) then days_in := 366 else days_in := 365; end; {days_in} begin bdos(105,addr(date_time)); { get system date/time } time := '00:00:00'; { initialize time } time[1] := chr((ord(date_time[3]) div 16) + 48); { hours first digit } time[2] := chr((ord(date_time[3]) mod 16) + 48); { second digit } time[4] := chr((ord(date_time[4]) div 16) + 48); { minutes first digit } time[5] := chr((ord(date_time[4]) mod 16) + 48); { second digit } year := 78; while (jdate > days_in(year)) do begin jdate := jdate-days_in(year); year := year + 1; end; if (leap(year)) then x := 2 { set proper date table } else x := 1; month := 1; while (jdate > day_table[x,month]) do { move us to the proper month } month := month + 1; if (month > 1) then jdate := jdate - day_table[x,month-1]; { and set the date } date := '00/00/00'; date[1] := chr(month div 10 + 48); { month first digit } date[2] := chr(month mod 10 + 48); { second digit } date[4] := chr(jdate div 10 + 48); { day first digit } date[5] := chr(jdate mod 10 + 48); { day second digit } date[7] := chr(year div 10 + 48); { year first digit } date[8] := chr(year mod 10 + 48); { second digit } end; { get_date } { PRINTUSE - print usage information and exit } procedure printuse; begin writeln; writeln('Turbo Pascal program listing and variable Cross-reference generator'); writeln; writeln('Usage is TYPEX [] [',optchr:1,']'); writeln(' Options are: I - INCLUDE files also'); writeln(' X - Create program Cross-reference'); write (' DEFAULTS: Output - '); writeln(default_output); write (' Includes - '); if include_default then writeln('YES') else writeln('NO'); write (' Xref - '); if xref_default then writeln('YES') else writeln('NO'); halt; end; { printuse } { OPENMAIN - Open main input and output files. Set XREF and INCLUDE options. } procedure openmain; var tmpstr, option_string : string[32]; param : byte; outfilename : filename; { output file name } function get_param(var param : byte) : string255; var x : byte; begin if (length(tmpstr) > 0) then begin { there's an option string here } get_param := tmpstr; tmpstr := ''; end else if (param > paramcount) then get_param := '' { no more parameters } else begin tmpstr := paramstr(param); { get next parameter } param := param+1; { bump parameter count } x := pos(optchr,tmpstr); if (x > 1) then { see if it's an option string } begin get_param := copy(tmpstr,1,x-1); { this is the returned parameter } tmpstr := copy(tmpstr,x,length(tmpstr)-x+1); { save this for next time } end else begin get_param := tmpstr; { return this } tmpstr := ''; { nothing saved } end; end; end; { get_param } begin { openmain } if (paramcount = 0) then printuse; includes := include_default; { set default parameters } xref := xref_default; tmpstr := ''; option_string := ''; param := 1; mainfilename := get_param(param); { get input file name } if not (open(mainfile,mainfilename)) then begin writeln('ERROR - cannot open input file ',mainfilename); halt; end; outfilename := get_param(param); { get output file name and options } if (length(outfilename) > 0) then if (outfilename[1] = optchr) then begin option_string := outfilename; { options } outfilename := default_output; { but no defined file name } end else option_string := get_param(param) { get options (if any) } else begin option_string := ''; { no options } outfilename := default_output; { no defined file name } end; assign(outfile,outfilename); {$I-} rewrite(outfile); {$I+} if (ioresult <> 0) then begin writeln('ERROR - cannot open output file ',outfilename); halt; end; if (pos(optchr,option_string) = 1) then begin { set options } includes := (include_default xor (pos('I',option_string) > 0)); xref := (xref_default xor (pos('X',option_string) > 0)); end; end {openmain}; begin {initialize} openmain; { open files and get options } get_date(date,time); { get date and time for headings } fillchar(dots,sizeof(dots),'.'); dots[0] := chr(70); { set length of dot line } search[1] := '{$'+'i'; search[2] := '{$'+'I'; search[3] := '(*$'+'i'; { setup search strings for includes } search[4] := '(*$'+'I'; page_no := 0; headings; xref_head := nil; end; {initialize} { PROCESS_FILE - print each line of the input file and INCLUDED files, if requested. Create cross-reference records for each variable if requested. } procedure process_file; var linebuffer : strptr; line_no, { current line number in input file } include_line : integer; { line number in include file } including, { TRUE = processing include file } quote : boolean; { quote flag } comment_type : byte; { type of comment being processed: 0 = no comment 1 = '{'-type comment 2 = '(*'-type comment } { INCLUDEIN - return TRUE if there is an INCLUDE statement in the current line } function includein(curstr : strptr) : boolean; var x, column : byte; begin x := 0; column := 0; repeat x := x+1; column := pos(search[x],curstr^); until (x = 4) or (column > 0); if (column = 0) then includein := false else includein := not (curstr^[column+length(search[x])] in ['-','+']); end; {includein} { PROCESS_LINE - write PRINTSTR to the output file, updating work_line. If cross-referencing, generate XREF records for each item found in PRINTSTR } procedure process_line(printstr : strptr; var work_line : integer); var x : byte; { XREF_LINE - create reference records for each item found in PRINTSTR } procedure xref_line; var x : byte; wkstr : string255; ch : char; { ADD_TREE - add a reference to the tree. If WKSTR is not in the tree, create a new node for it. } procedure add_tree(var tree : itmptr); var q,p : itmptr; less, found : boolean; { MAKETREE - create a new tree node. } function maketree : itmptr; var p : itmptr; begin {maketree} new(p); with p^ do begin getmem(idname,length(wkstr)+1); { allocate just enough for IDNAME } idname^ := wkstr; if (length(idname^) < max_id_len) then numrefs := 0 else numrefs := refs_per_line; left := nil; right := nil; rthrd := false; nxtptr := nil; { set reference pointer } fstptr := nil; end; maketree := p; end; {maketree} procedure setleft(p : itmptr); var q : itmptr; begin {setleft} q := maketree; p^.left := q; q^.right := p; { inorder successor of q is p } q^.rthrd := true; end; {setleft} procedure setright(p : itmptr); var q : itmptr; begin {setright} q := maketree; q^.right := p^.right; { inorder successor of q is successor of p } q^.rthrd := p^.rthrd; { may or may not be thread pointer } p^.right := q; p^.rthrd := false; end; {setright} procedure add_ref(p : itmptr; line_no,include_line : integer); var r : refptr; begin {add_ref} new(r); { create a new reference record } with r^ do begin line := line_no; incl := include_line; nxtptr := nil; end; with p^ do begin if (fstptr = nil) then { if first reference for this record } fstptr := r { setup list head pointer } else nxtptr^.nxtptr := r; { link previous last ref to new } nxtptr := r; { point to last } if (include_line > 0) then { update reference counter } numrefs := numrefs+2 { INCLUDEs take 2 spaces } else numrefs := numrefs+1; end; end; {add_ref} begin {add_tree} if tree = nil then begin { nothing in the tree } tree := maketree; { so we'll make it } p := tree; end else begin q := tree; p := tree; found := false; while (q <> nil) and not found do { search the tree } begin p := q; if (p^.idname^ = wkstr) then found := true { found it } else begin less := (wkstr < p^.idname^); if (less) then q := p^.left else if (p^.rthrd) then q := nil else q := p^.right; end; end; if (not found) then { not found, create a new node } if (less) then begin setleft(p); p := p^.left; end else begin setright(p); p := p^.right; end; end; add_ref(p,line_no,include_line); { create a new reference record } end; {add_tree} { GETCHR - get the next character in the line. Return 0 at end of line } procedure getchr; begin if (x = 0) or (x > length(printstr^)) then x := 0 { end of line } else begin ch := upcase(printstr^[x]); { convert to uppercase for xref } x := x+1; end; end; { KEYWORD - return TRUE if WKSTR is in the key word table. This is a simple binary search } function keyword : boolean; const nkwords = 44; { number of key words in table } type key_word_table= array[1..nkwords] of string[9]; const key_words : key_word_table = ('ABSOLUTE' ,'AND' ,'ARRAY' ,'BEGIN', 'CASE' ,'CONST' ,'DIV' ,'DO', 'DOWNTO' ,'ELSE' ,'END' ,'EXTERNAL', 'FILE' ,'FOR' ,'FORWARD' ,'FUNCTION', 'GOTO' ,'IF' ,'IN' ,'INLINE', 'LABEL' ,'MOD' ,'NIL' ,'NOT', 'OF' ,'OR' ,'OVERLAY' ,'PACKED', 'PROCEDURE','PROGRAM' ,'RECORD' ,'REPEAT', 'SET' ,'SHL' ,'SHR' ,'STRING', 'THEN' ,'TO' ,'TYPE' ,'UNTIL', 'VAR' ,'WHILE' ,'WITH' ,'XOR'); var high, low, mid : byte; begin high := nkwords; low := 1; while (low <= high) do begin mid := (high+low) div 2; if (key_words[mid] = wkstr) then begin keyword := true; exit; end else if (key_words[mid] > wkstr) then high := mid-1 else low := mid+1; end; keyword := false; end; begin {xref_line} x := 1; { start at beginning } wkstr := ''; getchr; while (x > 0) do { while not end of line } begin if (ch = '''') and (comment_type = 0) then { set quote flag } quote := not(quote) else if not quote then { if not in quote then go } case comment_type of 0 : if ch = '{' then comment_type := 1 { start a comment } else if ch = '(' then begin getchr; if (x > 0) then if (ch = '*') then comment_type := 2 { start a comment } else x := x-1; end else if ch in ['A'..'Z'] then { start a word } begin repeat wkstr := wkstr+ch; getchr; until (not (ch in ['0'..'9','A'..'Z','_'])) or (x = 0); if not keyword then { check for keyword } add_tree(xref_head);{ not keyword, add to xref tree } wkstr := ''; if x > 0 then { if not end of line } x := x-1; { go back to previous character } end; 1 : if ch = '}' then { end comment } comment_type := 0; 2 : if ch = '*' then begin getchr; if (x > 0) then if (ch = ')') then comment_type := 0 { end comment } else x := x-1; end; end; { case } getchr; end; { while } end; {xref_line} { FINDSPACE - find end of last full word that will fit on the line } function findspace(printstr : strptr; var x : byte) : byte; var y : byte; begin y := x; x := x+printwidth; if (x > length(printstr^)) then { the whole line will fit } x := length(printstr^)+1 else begin while (printstr^[x] <> ' ') and (x > y) do { look back for first space } x := x-1; if (x > y) then { found it } x := x+1 else x := y+printwidth+1; { no space, break in middle of word } end; findspace := x-1; end; {findspace} { DETAB - replace all tabs in the line with appropriate number of spaces } procedure detab(var printstr : string255); type string8 = string[8]; const tab = ^I; tab_string : string8 = ' '; var x : byte; begin x := pos(tab,printstr); while (x > 0) do begin delete(printstr,x,1); { remove the tab } insert(copy(tab_string,1,8-((x-1) mod 8)),printstr,x); { insert spaces } x := pos(tab,printstr); end; end; {detab} begin {process_line} detab(printstr^); currow := currow + ((length(printstr^)-1) div printwidth) + 1; if currow > printlength then begin headings; currow := currow + ((length(printstr^)-1) div printwidth) + 1; end; work_line := work_line + 1; if including then write(outfile,'<',work_line:5,'> : ') else write(outfile,' ',work_line:5,' : '); x := 1; writeln(outfile,copy(printstr^,1,findspace(printstr,x))); while x <= length(printstr^) do writeln(outfile,' ':10,copy(printstr^,x,findspace(printstr,x))); if xref then xref_line; end; {process_line} procedure process_include_file(incstr : strptr); var namestart, nameend : integer; includefile : text; includefilename : filename; function parse(incstr : strptr) : filename; begin namestart := pos('$I',incstr^)+2; if namestart = 2 then namestart := pos('$i',incstr^)+2; while (incstr^[namestart] = ' ') do namestart := namestart + 1; nameend := namestart; while (not (incstr^[nameend] in [' ','}','*'])) and ((nameend - namestart) <= pathlength) do nameend := nameend + 1; nameend := nameend - 1; parse := copy(incstr^,namestart,(nameend-namestart+1)); end; {parse} begin {process_include_file} includefilename := parse(incstr); if (pos('.',includefilename) = 0) then includefilename := includefilename + '.PAS'; including := true; include_line := 0; if not open(includefile,includefilename) then begin linebuffer^ := 'ERROR -- Include file not found: ' + includefilename; process_line(linebuffer,include_line); end else begin while not eof(includefile) do begin readln(includefile,linebuffer^); process_line(linebuffer,include_line); end; close(includefile); end; including := false; include_line := 0; end; {process_include_file} begin {process_file} new(linebuffer); quote := false; comment_type := 0; line_no := 0; include_line := 0; including := false; { not including a file now } while not eof(mainfile) do begin readln(mainfile,linebuffer^); process_line(linebuffer,line_no); if includes and includein(linebuffer) then process_include_file(linebuffer); end; dispose(linebuffer); end; {process_file} { PRINT_XREF - print the cross-reference listing } procedure print_xref(xref_head : itmptr); var ref_count : integer; p,q : itmptr; { LPWRITELN - write a newline on output file. Check for page break. } procedure lpwriteln; begin if (currow > printlength) then headings; { new page } writeln(outfile); currow := currow + 1; end; { NEWLINE - need another line for references. Start at position (MAX_ID_LEN+1) } procedure newline; begin lpwriteln; write(outfile,' ':(max_id_len + 1)); ref_count := 1; end; { PRINT_REFS - Print the list of references for the current node. } procedure print_refs(node : itmptr); var list : refptr; { WRITE_REF - output one reference to the print file } procedure write_ref(ref : refptr); var inclstr : string8; inclen : byte absolute inclstr; {easier than length(inclstr)} begin with ref^ do begin if (ref_count > refs_per_line) then newline; write(outfile,line:1); if (incl = 0) then begin { no include in this reference } str(line:1,inclstr); if (inclen < 6) then write(outfile,' ':(6-inclen)); ref_count := ref_count + 1; end else begin { process INCLUDEd reference } write(outfile,'<',incl:1,'>'); str(line:1,inclstr); if (inclen < 6) then write(outfile,' ':(6-inclen)); str(incl:1,inclstr); if (inclen < 4) then write(outfile,' ':(4-inclen)); ref_count := ref_count + 2; end; end; {with} end; {write_ref} begin {print_refs} if ((node^.numrefs div refs_per_line) > (printlength - currow)) then headings; write(outfile,node^.idname^); { output idname } if (length(node^.idname^) >= max_id_len) then newline else write(outfile,' ':(max_id_len-length(node^.idname^)+1)); ref_count := 1; list := node^.fstptr; repeat write_ref(list); list := list^.nxtptr; until (list = nil); lpwriteln; end; {print_refs} { in-order traversal of a right in-threaded binary tree. } begin {print_xref} headings; p := xref_head; repeat q := nil; while (p <> nil) do begin { traverse left branch } q := p; p := p^.left; end; if (q <> nil) then begin print_refs(q); p := q^.right; while (q^.rthrd) do begin { back up } print_refs(p); q := p; p := p^.right; end; end; until (q = nil); end; {print_xref} begin { typex } writeln('[TYPEX Version ',version_no,']'); initialize; process_file; if xref then print_xref(xref_head); page(outfile); close(mainfile); close(outfile); end. { typex }