<> <> <> DIRECTORY Atom USING [MakeAtom], Basics USING [], BasicTime USING [GMT, Now, nullGMT], Commander USING [CommandProc, Register], CommandTool USING [ArgumentVector, Failed, Parse], Convert USING [AppendInt], DFUtilities USING [DirectoryItem, FileItem, IncludeItem, ParseFromStream, ProcessItemProc, SyntaxError], FileNames USING [FileWithSearchRules, GetShortName], FS USING [EnumerateForNames, Error, GetName, NameProc, OpenFileFromStream, StreamOpen], IO USING [Close, EndOfStream, GetCedarToken, GetChar, GetIndex, PeekChar, PutChar, PutF1, PutFR, PutFR1, PutRope, SkipWhitespace, STREAM, TokenKind], List USING [Compare, CompareProc, PutAssoc, Sort], Process USING [CheckForAbort], RefText USING [InlineAppendChar, TrustTextAsRope], Rope USING [ActionType, Compare, Concat, Fetch, Flatten, FromRefText, Length, Map, Match, ROPE, SkipOver, SkipTo], SymTab USING [Create, FetchText, Ref, Store], TiogaAccess USING [Create, Put, TiogaChar, WriteFile, Writer]; XRef: CEDAR PROGRAM IMPORTS Atom, BasicTime, Commander, CommandTool, Convert, DFUtilities, FileNames, FS, IO, List, Process, RefText, Rope, SymTab, TiogaAccess = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; IdEntry: TYPE = RECORD [ name: ROPE, refs: LIST OF IdRef _ NIL, reserved: ATOM _ NIL]; IdRef: TYPE = RECORD [ fileName: ROPE, -- file name where reference occurs index: INT, -- index where reference occurs defn: BOOL]; -- indicates a defining reference DoCommandProc: Commander.CommandProc = { <<[cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL]>> <> out: STREAM = cmd.out; switches: PACKED ARRAY CHAR['a..'z] OF BOOL _ ALL[FALSE]; ProcessSwitches: PROC [arg: ROPE] = { sense: BOOL _ TRUE; FOR index: INT IN [0..Rope.Length[arg]) DO char: CHAR _ Rope.Fetch[arg, index]; SELECT char FROM '- => LOOP; '~ => {sense _ NOT sense; LOOP}; 'x, 'X => IF filesRead # 0 THEN DumpXref[xrefName]; IN ['a..'z] => switches[char] _ sense; IN ['A..'Z] => switches[char + ('a-'A)] _ sense; ENDCASE; sense _ TRUE; ENDLOOP; }; EachStream: PROC [stream: STREAM] = { <> blockLevel: INTEGER _ 0; parenLevel: INTEGER _ 0; lastId: REF IdEntry _ NIL; lastPos: INT _ 0; inDirectory: BOOL _ FALSE; inType: BOOL _ FALSE; AddRef: PROC [newRef: LIST OF IdRef] = { tail: LIST OF IdRef _ lastId.refs; IF tail = NIL THEN { idList _ CONS[lastId, idList]; newRef.rest _ newRef; uniqueTokensRead _ uniqueTokensRead + 1; } ELSE { newRef.rest _ tail.rest; tail.rest _ newRef; }; tokensRead _ tokensRead + 1; lastId.refs _ newRef; }; curFileName: ROPE _ FS.GetName[FS.OpenFileFromStream[stream]].fullFName; IF NOT switches['f] THEN curFileName _ FileNames.GetShortName[curFileName]; ReportName[curFileName]; filesRead _ filesRead + 1; IF fileListTail = NIL THEN fileListTail _ fileListHead _ LIST[curFileName] ELSE fileListTail _ (fileListTail.rest _ LIST[curFileName]); IF symTab = NIL THEN InitTable[]; IF switches['t] THEN { <> start: INT; DO c: CHAR _ IO.GetChar[stream ! IO.EndOfStream => EXIT]; SELECT c FROM IN ['a..'z], IN ['A..'Z] => {}; ENDCASE => LOOP; cToken[0] _ c; cToken.length _ 1; start _ IO.GetIndex[stream] - 1; DO c: CHAR _ IO.GetChar[stream ! IO.EndOfStream => EXIT]; SELECT c FROM IN ['a..'z], IN ['A..'Z], IN ['0..'9], '' => cToken _ RefText.InlineAppendChar[cToken, c]; ENDCASE => EXIT; ENDLOOP; WITH SymTab.FetchText[symTab, cToken].val SELECT FROM id: REF IdEntry => lastId _ id; ENDCASE => { new: REF IdEntry _ NEW[IdEntry _ [name: Rope.FromRefText[cToken]]]; [] _ SymTab.Store[symTab, new.name, new]; lastId _ new; }; AddRef[LIST[[curFileName, start, FALSE]]]; ENDLOOP; } ELSE { DO tokenKind: IO.TokenKind; Process.CheckForAbort[]; [token: cToken, tokenKind: tokenKind] _ IO.GetCedarToken[stream, cToken]; SELECT tokenKind FROM tokenSINGLE => { SELECT cToken[0] FROM '[, '( => parenLevel _ parenLevel + 1; '], ') => parenLevel _ parenLevel - 1; '{ => IF NOT inType THEN blockLevel _ blockLevel + 1; '} => IF NOT inType THEN { c: CHAR _ '.; c _ IO.PeekChar[stream ! IO.EndOfStream => CONTINUE]; blockLevel _ blockLevel - 1; IF blockLevel <= 0 OR c = '. THEN EXIT; }; ', => LOOP; '; => GO TO clearFlags; ENDCASE; EXITS clearFlags => inDirectory _ inType _ FALSE; }; tokenID => { WITH SymTab.FetchText[symTab, cToken].val SELECT FROM id: REF IdEntry => { SELECT id.reserved FROM NIL => { lastId _ id; GO TO addReference; }; $BEGIN => blockLevel _ blockLevel + 1; $END => { c: CHAR _ '.; c _ IO.PeekChar[stream ! IO.EndOfStream => CONTINUE]; blockLevel _ blockLevel - 1; IF blockLevel <= 0 OR c = '. THEN EXIT; }; $DIRECTORY => {inDirectory _ TRUE; GO TO retainDirectoryFlag}; $USING => GO TO retainDirectoryFlag; $TYPE => {inType _ TRUE; GO TO retainDirectoryFlag}; ENDCASE; inDirectory _ FALSE; EXITS retainDirectoryFlag => {}; }; ENDCASE => { new: REF IdEntry _ NEW[IdEntry _ [name: Rope.FromRefText[cToken]]]; [] _ SymTab.Store[symTab, new.name, new]; lastId _ new; GO TO addReference; }; EXITS addReference => { ENABLE IO.EndOfStream => LOOP; index: INT _ IO.GetIndex[stream]-cToken.length; defn: BOOL _ FALSE; c: CHAR _ 0C; [] _ IO.SkipWhitespace[stream ! IO.EndOfStream => CONTINUE]; c _ IO.PeekChar[stream ! IO.EndOfStream => CONTINUE]; IF c = ': THEN <> IF parenLevel = 0 THEN defn _ TRUE; AddRef[LIST[[curFileName, index, defn]]]; LOOP; }; }; tokenERROR => EXIT; tokenEOF => EXIT; ENDCASE; lastId _ NIL; ENDLOOP; }; IO.Close[stream]; }; InitTable: PROC = { reserved: ROPE _ "RECORD POINTER REF VAR LIST ARRAY SEQUENCE DESCRIPTOR PROCEDURE PROC PORT SIGNAL ERROR PROCESS PROGRAM MONITOR DEFINITIONS ZONE RELATIVE LONG TYPE FRAME TO ORDERED UNCOUNTED PAINTED BASE OF PACKED RETURNS SAFE UNSAFE MONITORED OVERLAID COMPUTED MACHINE DEPENDENT DIRECTORY IMPORTS EXPORTS SHARES LOCKS USING PUBLIC PRIVATE CEDAR CHECKED TRUSTED UNCHECKED ENTRY INTERNAL INLINE READONLY CODE ABS ALL AND APPLY CONS MAX MIN MOD NOT OR ORD PRED LENGTH NEW START SUCC VAL FORK JOIN LOOPHOLE NARROW ISTYPE SIZE FIRST LAST NIL TRASH NULL IF THEN ELSE WITH FROM FOR DECREASING IN THROUGH UNTIL WHILE REPEAT FINISHED RETURN EXIT LOOP GOTO GO FREE WAIT RESTART NOTIFY BROADCAST STOP RESUME REJECT CONTINUE RETRY TRANSFER STATE OPEN ENABLE ANY EXITS END ENDLOOP ENDCASE BEGIN DO SELECT "; <> <> builtIn: ROPE _ "TRUE FALSE INT CARD INTEGER CARDINAL BOOL BOOLEAN UNSPECIFIED CHARACTER CHAR WORD REAL NAT TEXT STRING MONITORLOCK CONDITION MDSZone StringBody ATOM UNWIND ABORTED "; <> <> symTab _ SymTab.Create[1253]; AddReserved[symTab, reserved]; AddReserved[symTab, builtIn]; }; ReportName: PROC [name: ROPE] = { THROUGH [0..indent) DO IO.PutChar[out, ' ]; ENDLOOP; IO.PutF1[out, " reading %g\n", [rope[name]]]; }; DumpXref: PROC [arg: ROPE] = { <> tc: TiogaAccess.TiogaChar _ [ charSet: 0, char: '\n, looks: ALL[FALSE], format: NIL, comment: FALSE, endOfNode: TRUE, deltaLevel: 1, propList: List.PutAssoc[key: $FromTiogaFile, val: $Yes, aList: NIL] ]; PutRope: PROC [rope: ROPE] = { [] _ Rope.Map[base: rope, action: PutCharB]; }; PutRopeBold: PROC [rope: ROPE] = { tc.looks['n] _ TRUE; [] _ Rope.Map[base: rope, action: PutCharB]; tc.looks['n] _ FALSE; }; PutRopeItalic: PROC [rope: ROPE] = { tc.looks['c] _ TRUE; [] _ Rope.Map[base: rope, action: PutCharB]; tc.looks['c] _ FALSE; }; PutCharB: Rope.ActionType = { <<[c: CHAR] RETURNS [quit: BOOL _ FALSE]>> tc.char _ c; TiogaAccess.Put[writer, tc]; }; PutChar: PROC [c: CHAR] = { tc.char _ c; TiogaAccess.Put[writer, tc]; }; EndNode: PROC [delta: INTEGER _ 0, format: ATOM _ NIL] = { tc.endOfNode _ TRUE; tc.char _ '\n; tc.format _ format; tc.deltaLevel _ delta; TiogaAccess.Put[writer, tc]; tc.endOfNode _ FALSE; }; writer: TiogaAccess.Writer _ TiogaAccess.Create[]; buffer: REF TEXT _ NEW[TEXT[12]]; IF filesRead = 1 THEN IO.PutRope[out, " 1 file read\n"] ELSE IO.PutF1[out, " %g files read\n", [integer[filesRead]] ]; TiogaAccess.Put[writer, tc]; tc.comment _ TRUE; tc.endOfNode _ FALSE; tc.propList _ NIL; PutRope[arg]; EndNode[1]; PutRope[IO.PutFR1["taken on %g", [time[BasicTime.Now[]]]]]; EndNode[0]; PutRope[IO.PutFR["# files: %g, # tokens: %g, # unique tokens: %g", [integer[filesRead]], [integer[tokensRead]], [integer[uniqueTokensRead]] ]]; EndNode[1]; FOR each: LIST OF ROPE _ fileListHead, each.rest WHILE each # NIL DO PutRope[each.first]; EndNode[0]; ENDLOOP; EndNode[-2]; tc.comment _ FALSE; idList _ List.Sort[idList, MySort]; WHILE idList # NIL DO each: REF IdEntry _ NARROW[idList.first]; tail: LIST OF IdRef _ each.refs; IF tail # NIL THEN { refs: LIST OF IdRef _ tail.rest; fileName: ROPE _ NIL; tail.rest _ NIL; each.refs _ NIL; PutRopeBold[each.name]; PutRopeBold[":"]; IF filesRead > 1 THEN EndNode[1, $code]; WHILE refs # NIL DO index: INT = refs.first.index; tail _ refs.rest; IF filesRead > 1 AND refs.first.fileName # fileName THEN { IF fileName # NIL THEN EndNode[0, $code]; fileName _ refs.first.fileName; PutRopeItalic[fileName]; PutChar[' ]; }; buffer.length _ 0; [] _ Convert.AppendInt[buffer, ABS[index], 10, FALSE]; PutChar[' ]; IF refs.first.defn THEN PutRopeBold[RefText.TrustTextAsRope[buffer]] ELSE PutRope[RefText.TrustTextAsRope[buffer]]; refs.rest _ NIL; refs _ tail; ENDLOOP; EndNode[IF filesRead > 1 THEN -1 ELSE 0, $code]; }; idList _ idList.rest; ENDLOOP; IO.PutF1[out, " writing to %g\n", [rope[arg]]]; TiogaAccess.WriteFile[writer, arg]; symTab _ NIL; xrefName _ NIL; filesRead _ 0; }; indent: INTEGER _ 0; symTab: SymTab.Ref _ NIL; idList: LIST OF REF ANY _ NIL; cToken: REF TEXT _ NEW[TEXT[128]]; xrefName: ROPE _ NIL; filesRead: INT _ 0; tokensRead: INT _ 0; uniqueTokensRead: INT _ 0; fileListHead: LIST OF ROPE _ NIL; fileListTail: LIST OF ROPE _ NIL; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd: cmd, starExpand: FALSE ! CommandTool.Failed => {msg _ errorMsg; GO TO failed}]; <> FOR i: NAT IN [1..argv.argc) DO <> arg: ROPE _ argv[i]; Process.CheckForAbort[]; <> SELECT TRUE FROM Rope.Length[arg] = 0 => {}; Rope.Fetch[arg, 0] = '- => ProcessSwitches[arg]; ENDCASE => { { ENABLE { FS.Error => {msg _ error.explanation; GO TO cleanUp}; DFUtilities.SyntaxError => {msg _ reason; GO TO cleanUp}; }; EachFile: DFUtilities.ProcessItemProc = { <<[item: REF ANY] RETURNS [stop: BOOL _ FALSE]>> WITH item SELECT FROM fileItem: REF DFUtilities.FileItem => SELECT TRUE FROM Rope.Match["*.mesa!*", fileItem.name, FALSE], Rope.Match["*.mesa", fileItem.name, FALSE] => { sName: ROPE = FileNames.GetShortName[fileItem.name]; name: ROPE _ sName; date: BasicTime.GMT _ BasicTime.nullGMT; st: STREAM _ NIL; IF NOT switches['h] THEN { name _ Rope.Concat[path, sName]; date _ fileItem.date.gmt; }; st _ FS.StreamOpen[ fileName: name, wantedCreatedTime: date, remoteCheck: FALSE ! FS.Error => IF error.code = $unknownFile AND switches['h] THEN CONTINUE]; IF st = NIL THEN { <> name _ Rope.Concat[path, sName]; st _ FS.StreamOpen[fileName: name]; }; EachStream[st]; }; ENDCASE; dirItem: REF DFUtilities.DirectoryItem => path _ dirItem.path1; inclItem: REF DFUtilities.IncludeItem => EachDF[inclItem.path1, inclItem.date.gmt]; ENDCASE; }; EachDF: PROC [dfName: ROPE, date: BasicTime.GMT _ BasicTime.nullGMT] = { inclStream: STREAM _ NIL; sName: ROPE = FileNames.GetShortName[dfName]; ReportName[sName]; inclStream _ FS.StreamOpen[fileName: dfName, wantedCreatedTime: date]; indent _ indent + 2; DFUtilities.ParseFromStream[ inclStream, EachFile, [filterA: source, filterC: defining] ! UNWIND => IO.Close[inclStream];]; IO.Close[inclStream]; indent _ indent - 2; }; EachName: FS.NameProc = { <<[fullFName: ROPE] RETURNS [continue: BOOL]>> name: ROPE _ NIL; continue _ TRUE; IF xrefName = NIL THEN { sName: ROPE _ FileNames.GetShortName[fullFName]; pos: INT _ Rope.Length[sName]; xrefName _ Rope.Flatten[sName]; WHILE pos > 0 DO SELECT Rope.Fetch[xrefName, pos-1] FROM '! => xrefName _ Rope.Flatten[xrefName, 0, pos-1]; '. => {xrefName _ Rope.Flatten[xrefName, 0, pos-1]; EXIT}; ENDCASE; pos _ pos - 1; ENDLOOP; xrefName _ Rope.Concat[xrefName, ".xref"]; }; SELECT TRUE FROM (name _ FileNames.FileWithSearchRules[root: fullFName, defaultExtension: ".mesa", searchRules: NIL].fullPath) # NIL => EachStream[FS.StreamOpen[name]]; (name _ FileNames.FileWithSearchRules[root: fullFName, defaultExtension: ".tioga", searchRules: NIL].fullPath) # NIL => EachStream[FS.StreamOpen[name]]; (name _ FileNames.FileWithSearchRules[root: fullFName, defaultExtension: ".df", searchRules: NIL].fullPath) # NIL => IF switches['s] THEN EachStream[FS.StreamOpen[name]] ELSE EachDF[name]; switches['t] => EachStream[FS.StreamOpen[fullFName]]; ENDCASE => IO.PutF1[out, "-- Warning: %g was not found.\n", [rope[fullFName]] ]; }; path: ROPE _ NIL; IF Rope.SkipTo[arg, 0, "*"] # Rope.Length[arg] THEN { <> IF Rope.SkipTo[arg, 0, "!"] = Rope.Length[arg] THEN arg _ Rope.Concat[arg, "!h"]; FS.EnumerateForNames[arg, EachName]; } ELSE <> [] _ EachName[arg]; EXITS cleanUp => { <> FOR each: LIST OF REF ANY _ idList, each.rest WHILE each # NIL DO WITH each.first SELECT FROM entry: REF IdEntry => { ring: LIST OF IdRef _ entry.refs; entry.refs _ NIL; IF ring # NIL THEN ring.rest _ NIL; }; ENDCASE; ENDLOOP; symTab _ NIL; GO TO failed; }; }; }; ENDLOOP; IF filesRead # 0 THEN DumpXref[xrefName]; EXITS failed => {result _ $Failure}; }; AddReserved: PROC [symTab: SymTab.Ref, rope: ROPE] = { pos: INT _ 0; len: INT _ Rope.Length[rope]; DO next: INT _ Rope.SkipTo[rope, pos, " "]; flat: ROPE _ Rope.Flatten[rope, pos, next-pos]; new: REF IdEntry _ NEW[IdEntry _ [name: flat, reserved: Atom.MakeAtom[flat]]]; [] _ SymTab.Store[symTab, flat, new]; pos _ Rope.SkipOver[rope, next, " "]; IF pos = len THEN EXIT; ENDLOOP; }; MySort: List.CompareProc = { <<[ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison]>> name1: ROPE = NARROW[ref1, REF IdEntry].name; name2: ROPE = NARROW[ref2, REF IdEntry].name; IF name1 = name2 THEN RETURN [equal]; SELECT Rope.Compare[name1, name2, FALSE] FROM less => RETURN [less]; greater => RETURN [greater]; ENDCASE => RETURN [Rope.Compare[name1, name2, TRUE]]; }; doc: ROPE = "takes cross-references of Cedar source files listed in the given DF files -f: print full file names -h: use local highest versions of files -s: shallow, don't follow df files -t: text file, not Cedar tokens"; Commander.Register[ key: "///Commands/XRef", <> proc: DoCommandProc, <> doc: doc, clientData: NIL, interpreted: TRUE ]; END. <<>>