XRef.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Russ Atkinson (RRA) September 30, 1986 3:46:41 pm PDT
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: ATOMNIL];
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: REFNIL, msg: ROPENIL]
CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...]
out: STREAM = cmd.out;
switches: PACKED ARRAY CHAR['a..'z] OF BOOLALL[FALSE];
ProcessSwitches: PROC [arg: ROPE] = {
sense: BOOLTRUE;
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] = {
Takes an open stream and adds the info to the xref.
blockLevel: INTEGER ← 0;
parenLevel: INTEGER ← 0;
lastId: REF IdEntry ← NIL;
lastPos: INT ← 0;
inDirectory: BOOLFALSE;
inType: BOOLFALSE;
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: ROPEFS.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 {
text, not Cedar/Mesa
start: INT;
DO
c: CHARIO.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: CHARIO.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: INTIO.GetIndex[stream]-cToken.length;
defn: BOOLFALSE;
c: CHAR ← 0C;
[] ← IO.SkipWhitespace[stream ! IO.EndOfStream => CONTINUE];
c ← IO.PeekChar[stream ! IO.EndOfStream => CONTINUE];
IF c = ': THEN
Could be a reference
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 ";
Cedar/Mesa reserved words
derived from Pass1T.mesa
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 ";
Cedar/Mesa built-in types
derived from Pass1.mesa
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] = {
Now we can put out the cross-reference
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: ATOMNIL] = {
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 TEXTNEW[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: ROPENIL;
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 ANYNIL;
cToken: REF TEXTNEW[TEXT[128]];
xrefName: ROPENIL;
filesRead: INT ← 0;
tokensRead: INT ← 0;
uniqueTokensRead: INT ← 0;
fileListHead: LIST OF ROPENIL;
fileListTail: LIST OF ROPENIL;
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd: cmd, starExpand: FALSE
! CommandTool.Failed => {msg ← errorMsg; GO TO failed}];
When parsing the command line, be prepared for failure. The error is reported to the user
FOR i: NAT IN [1..argv.argc) DO
Each argument can either be a switch specification or a genuine argument to be processed. The first argument (argv[0]) is not examined, because by convention it is the name of the command as given by the user.
arg: ROPE ← argv[i];
Process.CheckForAbort[];
It is a good idea to periodically check for a process abort request.
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: STREAMNIL;
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 {
The local open failed because there was no local version, so try the remote version (with the highest version number).
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: STREAMNIL;
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: ROPENIL;
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: ROPENIL;
IF Rope.SkipTo[arg, 0, "*"] # Rope.Length[arg]
THEN {
A pattern to enumerate
IF Rope.SkipTo[arg, 0, "!"] = Rope.Length[arg] THEN
arg ← Rope.Concat[arg, "!h"];
FS.EnumerateForNames[arg, EachName];
}
ELSE
Appears to be a single file
[] ← EachName[arg];
EXITS cleanUp => {
The idea is to charge down the list, breaking the circularities as we go.
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",
key is the name of the command. Registering the command under the "///Commands/" directory indicates that this command should be globally known and useful. If no directory is given, the command is registered under the current working directory, which is the proper place for more specific commands.
proc: DoCommandProc,
proc is called to execute the command.
doc: doc,
clientData: NIL,
interpreted: TRUE
];
END.