UsingDependenciesImpl.mesa
Copyright (C) 1984, 1985, Xerox Corporation. All rights reserved.
Michael Plass, June 28, 1985 2:58:35 pm PDT
Pier, February 6, 1986 2:10:38 pm PST
DIRECTORY
Ascii USING [CR, LF, SP, TAB],
Basics,
BasicTime,
Commander USING [CommandProc, Handle, Register],
CommandTool USING [DoCommand],
FS,
FileNames,
Interpreter,
IO,
SymTab,
RefText,
TiogaAccess,
List,
Rope;
UsingDependenciesImpl: CEDAR PROGRAM
IMPORTS BasicTime, IO, FS, FileNames, Commander, CommandTool, Interpreter, RefText, Rope, List, TiogaAccess, SymTab
~ BEGIN
ROPE: TYPE ~ Rope.ROPE;
Scanner: TYPE ~ REF ScannerRep;
ScannerRep: TYPE ~ RECORD [
stream: IO.STREAM,
tokenKind: Kind,
inputBuffer: REF TEXT,
locationOfBufferStart: INT,
tokenStart: NAT,
tokenLength: NAT
];
inputBufferSize: NAT ← 2*1024;
CreateScanner: PROC RETURNS [scanner: Scanner] ~ {
scanner ← NEW[ScannerRep];
scanner.inputBuffer ← NEW[TEXT[inputBufferSize]];
};
SetScanner: PROC [scanner: Scanner, stream: IO.STREAM] ~ {
scanner.stream ← stream;
scanner.tokenKind ← whitespace;
scanner.locationOfBufferStart ← IO.GetIndex[stream];
scanner.tokenStart ← 0;
scanner.tokenLength ← 0;
scanner.inputBuffer.length ← 0;
};
Kind: TYPE ~ {identifier, special, charconst, string, whitespace, newline, comment, eof};
KindOf: PROC [c: CHAR] RETURNS [Kind] ~ {
SELECT c FROM
IN ['a..'z], IN ['A..'Z], IN ['0..'9], '$ => RETURN [identifier];
Ascii.SP, Ascii.TAB, Ascii.LF => RETURN [whitespace];
Ascii.CR => RETURN [newline];
ENDCASE => RETURN [special];
};
breakLoc: INTLAST[INT];
breakCount: INT ← 0;
GetCharConstant: PROC [scanner: Scanner] ~ {
IF scanner.tokenStart>=scanner.inputBuffer.length-10 THEN RefillBuffer[scanner];
scanner.tokenKind ← charconst;
scanner.tokenLength ← 2;
IF scanner.inputBuffer[scanner.tokenStart+1] = '\\ THEN {
IF scanner.inputBuffer[scanner.tokenStart+scanner.tokenLength] IN ['0..'7] THEN {
WHILE scanner.inputBuffer[scanner.tokenStart+scanner.tokenLength] IN ['0..'7] DO
scanner.tokenLength ← scanner.tokenLength + 1;
ENDLOOP;
}
ELSE scanner.tokenLength ← 3;
};
};
GetStringConstant: PROC [scanner: Scanner] ~ {
GetC: PROC ~ {
IF scanner.tokenStart+scanner.tokenLength>=scanner.inputBuffer.length-10 THEN RefillBuffer[scanner];
scanner.tokenLength ← scanner.tokenLength + 1;
IF scanner.inputBuffer[scanner.tokenStart+scanner.tokenLength] = '\\ THEN {
scanner.tokenLength ← scanner.tokenLength + 1;
IF scanner.inputBuffer[scanner.tokenStart+scanner.tokenLength] IN ['0..'7] THEN {
WHILE scanner.inputBuffer[scanner.tokenStart+scanner.tokenLength] IN ['0..'7] DO
scanner.tokenLength ← scanner.tokenLength + 1;
ENDLOOP;
}
ELSE scanner.tokenLength ← scanner.tokenLength + 1;
};
};
scanner.tokenLength ← 0;
GetC[];
UNTIL scanner.inputBuffer[scanner.tokenStart+scanner.tokenLength] = '" DO
GetC[];
ENDLOOP;
scanner.tokenLength ← scanner.tokenLength + 1;
scanner.tokenKind ← string;
};
GetComment: PROC [scanner: Scanner] ~ {
c: CHAR ← ' ; -- next char after the current end-of-token
cc: CHAR ← ' ; -- char after c
PeekCC: PROC ~ {
IF scanner.tokenStart+scanner.tokenLength+2>=scanner.inputBuffer.length THEN RefillBuffer[scanner];
c ← cc ← '\n;
IF scanner.tokenStart+scanner.tokenLength<scanner.inputBuffer.length THEN
c ← scanner.inputBuffer[scanner.tokenStart+scanner.tokenLength];
IF scanner.tokenStart+scanner.tokenLength+1<scanner.inputBuffer.length THEN
cc ← scanner.inputBuffer[scanner.tokenStart+scanner.tokenLength+1];
};
scanner.tokenLength ← 2;
PeekCC[];
UNTIL c = '\n OR (c = '- AND cc = '-) DO
scanner.tokenLength ← scanner.tokenLength + 1;
PeekCC[];
ENDLOOP;
IF (c = '- AND cc = '-) THEN scanner.tokenLength ← scanner.tokenLength + 2;
scanner.tokenKind ← comment;
};
GetToken: PROC [scanner: Scanner] ~ {
Gets the next token into inputBuffer[tokenStart] .. inputBuffer[tokenLength-1]
c: CHAR;
scanner.tokenStart ← scanner.tokenStart+scanner.tokenLength;
scanner.tokenLength ← 0;
IF scanner.tokenStart>=scanner.inputBuffer.length-1 THEN RefillBuffer[scanner];
IF scanner.inputBuffer.length = 0 THEN {scanner.tokenKind ← eof; RETURN};
c ← scanner.inputBuffer[scanner.tokenStart];
IF c = '' THEN {GetCharConstant[scanner]; RETURN};
IF c = '" THEN {GetStringConstant[scanner]; RETURN};
IF c = '- AND scanner.inputBuffer[scanner.tokenStart+1] = '- THEN {GetComment[scanner]; RETURN};
scanner.tokenKind ← KindOf[scanner.inputBuffer[scanner.tokenStart]];
IF scanner.tokenKind = special THEN {
scanner.tokenLength ← 1;
RETURN;
};
WHILE scanner.tokenStart+scanner.tokenLength<scanner.inputBuffer.length
AND KindOf[scanner.inputBuffer[scanner.tokenStart+scanner.tokenLength]] = scanner.tokenKind DO
IF scanner.locationOfBufferStart+scanner.tokenStart+scanner.tokenLength = breakLoc THEN breakCount ← breakCount + 1;
scanner.tokenLength ← scanner.tokenLength + 1;
IF scanner.tokenStart + scanner.tokenLength = scanner.inputBuffer.length THEN {
IF IO.EndOf[scanner.stream] THEN RETURN;
RefillBuffer[scanner];
};
ENDLOOP;
};
RefillBuffer: PROC [scanner: Scanner] = {
residual: NAT ~ scanner.inputBuffer.length-scanner.tokenStart;
FOR i: NAT IN [0..residual) DO
scanner.inputBuffer[i] ← scanner.inputBuffer[i+scanner.tokenStart];
ENDLOOP;
scanner.inputBuffer.length ← residual;
scanner.locationOfBufferStart ← scanner.locationOfBufferStart+scanner.tokenStart;
scanner.tokenStart ← 0;
[] ← scanner.stream.GetBlock[block: scanner.inputBuffer, startIndex: residual, count: scanner.inputBuffer.maxLength-residual];
};
Match: PROC [scanner: Scanner, key: REF READONLY TEXT] RETURNS [equal: BOOLEAN] = {
i: NAT ← scanner.tokenStart;
IF key.length # scanner.tokenLength THEN RETURN [FALSE];
FOR j: NAT IN [0..key.length) DO
IF key[j]#scanner.inputBuffer[i] THEN RETURN [FALSE];
i←i+1;
ENDLOOP;
RETURN [TRUE]
};
MatchRope: PROC [scanner: Scanner, key: ROPE] RETURNS [equal: BOOLEAN] = {
i: NAT ← scanner.tokenStart;
IF key.Length # scanner.tokenLength THEN RETURN [FALSE];
FOR j: NAT IN [0..scanner.tokenLength) DO
IF key.Fetch[j]#scanner.inputBuffer[i] THEN RETURN [FALSE];
i←i+1;
ENDLOOP;
RETURN [TRUE]
};
IDRope: PROC [scanner: Scanner] RETURNS [rope: ROPE] = {
i: INTEGER ← scanner.tokenStart-1;
p: PROC RETURNS [CHAR] ~ {RETURN [scanner.inputBuffer[i←i+1]]};
IF scanner.tokenKind # identifier THEN RaiseError[scanner];
RETURN [Rope.FromProc[scanner.tokenLength, p]];
};
AppendID: PROC [text: REF TEXT, scanner: Scanner] RETURNS [REF TEXT] = {
RETURN [RefText.Append[to: text, from: scanner.inputBuffer, start: scanner.tokenStart, len: scanner.tokenLength]]
};
Pair: TYPE ~ RECORD [
member: ROPE,
owner: ROPE
];
ParseError: ERROR [sourceLoc: INT] = CODE;
RaiseError: PROC [scanner: Scanner] = {
ParseError[scanner.locationOfBufferStart+scanner.tokenStart];
};
RecordDependencies: PROC [scanner: Scanner, stem: ROPE, cmd: Commander.Handle, symTab: SymTab.Ref] ~ {
GetTok: PROC ~ {
GetToken[scanner];
WHILE scanner.tokenKind = comment OR scanner.tokenKind = whitespace OR scanner.tokenKind = newline DO
GetToken[scanner];
ENDLOOP;
};
text: REF TEXT ← RefText.ObtainScratch[100];
GetTok[];
UNTIL scanner.tokenKind = eof OR Match[scanner, "DIRECTORY"] DO
GetToken[scanner];
ENDLOOP;
IF scanner.tokenKind = eof THEN RaiseError[scanner];
GetTok[];
IF scanner.tokenKind = special AND scanner.inputBuffer[scanner.tokenStart] = '. THEN NULL
ELSE {
UNTIL Match[scanner, ";"] DO
text.length ← 0;
text ← AppendID[text, scanner];
GetTok[];
IF Match[scanner, ":"] THEN {
GetTok[];
IF NOT Match[scanner, "FROM"] THEN RaiseError[scanner];
GetTok[];
IF scanner.tokenKind # string THEN RaiseError[scanner];
text.length ← 0;
text ← RefText.Append[to: text, from: scanner.inputBuffer, start: scanner.tokenStart+1, len: scanner.tokenLength-2];
GetTok[];
};
IF NOT Match[scanner, "USING"] THEN RaiseError[scanner];
GetTok[];
IF NOT Match[scanner, "["] THEN RaiseError[scanner];
GetTok[];
UNTIL Match[scanner, "]"] DO
savedLength: NAT ← text.length;
val: REF LIST OF ROPENIL;
text ← RefText.AppendChar[text, '.];
text ← AppendID[text, scanner];
TRUSTED {
val ← NARROW[symTab.Fetch[LOOPHOLE[text]].val];
};
IF val = NIL THEN {
val ← NEW[LIST OF ROPENIL];
[] ← symTab.Store[Rope.FromRefText[text], val];
};
val^ ← CONS[stem, val^];
GetTok[];
IF Match[scanner, ","] THEN GetTok[]
ELSE IF NOT Match[scanner, "]"] THEN RaiseError[scanner];
text.length ← savedLength;
ENDLOOP;
GetTok[];
IF Match[scanner, ","] THEN GetTok[]
ELSE IF NOT Match[scanner, ";"] THEN RaiseError[scanner];
ENDLOOP;
};
RefText.ReleaseScratch[text];
};
Compare: PROC [ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison] ~ {
r1: LIST OF ROPENARROW[ref1];
r2: LIST OF ROPENARROW[ref2];
RETURN [Rope.Compare[r1.first, r2.first, FALSE]]
};
WriteResults: PROC [stream: IO.STREAM, symTab: SymTab.Ref, cmd: Commander.Handle] ~ {
list: LIST OF REFNIL;
EachPair: PROC [key: ROPE, val: REF] RETURNS [quit: BOOLFALSE] ~ {
refList: REF LIST OF ROPENARROW[val];
ropes: LIST OF ROPE ← refList^;
ropes ← CONS[key, ropes];
list ← CONS[ropes, list];
};
[] ← symTab.Pairs[EachPair];
list ← List.Sort[list, Compare];
FOR p: LIST OF REF ← list, p.rest UNTIL p = NIL DO
r: LIST OF ROPENARROW[p.first];
s: LIST OF ROPE ← DReverse[r.rest];
r.rest ← NIL;
stream.PutRope[r.first];
stream.PutRope[": "];
WHILE s # NIL DO
stream.PutRope[s.first];
stream.PutChar[' ];
{t: LIST OF ROPE ← s; s ← s.rest; t.rest ← NIL};
ENDLOOP;
IF doImpls THEN stream.PutRope[FindImplementor[r.first]];
stream.PutChar['\n];
ENDLOOP;
};
FindImplementor: PROC [def: ROPE] RETURNS [imp: ROPE] ~ {
this Proc calls the Interpreter to evaluate a rope of the form Interface.ProcName. If a rope of the form XXX.ProcName comes back, it is assumed that XXX is the implementor of Interface. Since it is possible to get other things back from the Interpreter, like types or errors, some defaults are returned ( -- or ??) instead of the interpreted result.
deftail, result: ROPENIL;
noResult: BOOLTRUE;
[result, , noResult] ← Interpreter.EvaluateToRope[def];
IF noResult OR result=NIL THEN RETURN["??"];
deftail ← FileNames.Tail[def, '.];
result ← IF Rope.Equal[deftail, FileNames.Tail[result, '.], TRUE] THEN Rope.Substr[base: result, start: 0, len: Rope.Length[result]-Rope.Length[deftail]-1] ELSE "!!";
RETURN[IF Rope.Length[result]=0 THEN "!!" ELSE result];
};
DReverse: PROC [old: LIST OF ROPE] RETURNS [new: LIST OF ROPENIL] ~ {
Destructive.
WHILE old # NIL DO
t: LIST OF ROPE ~ old;
old ← t.rest;
t.rest ← new;
new ← t;
ENDLOOP;
};
Break: PROC [char: CHAR] RETURNS [IO.CharClass] = {
IF char = '← OR char = '; THEN RETURN [break];
IF char = ' OR char = '  OR char = ', OR char = '\n THEN RETURN [sepr];
RETURN [other];
};
GetCmdToken: PROC [stream: IO.STREAM] RETURNS [rope: ROPENIL] ~ {
rope ← IO.GetTokenRope[stream, Break ! IO.EndOfStream => CONTINUE].token;
};
FindNewer: PROC [fileName: ROPE, newerThan: BasicTime.GMT] RETURNS [fullName: ROPENIL] ~ {
full: ROPENIL;
infoProc: FS.InfoProc ~ {IF BasicTime.Period[from: newerThan, to: created] > 0 THEN {fullName ← fullFName; continue ← FALSE}};
FS.EnumerateForInfo[fileName, infoProc];
};
UsingDependenciesCommand: Commander.CommandProc ~ {
Reformat: PROC [filename: ROPE] ~ {
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;
};
PutCharB: Rope.ActionType = {
[c: CHAR] RETURNS [quit: BOOL ← FALSE]
tc.char ← c;
TiogaAccess.Put[writer, tc];
};
PutRope: PROC [rope: ROPE] = {
[] ← Rope.Map[base: rope, action: PutCharB];
};
PutRopeItalic: PROC [rope: ROPE] = {
tc.looks['i] ← TRUE;
[] ← Rope.Map[base: rope, action: PutCharB];
tc.looks['i] ← FALSE;
};
PutRopeBold: PROC [rope: ROPE] = {
tc.looks['b] ← TRUE;
[] ← Rope.Map[base: rope, action: PutCharB];
tc.looks['b] ← FALSE;
};
tc: TiogaAccess.TiogaChar ← [
charSet: 0,
char: '\n,
looks: ALL[FALSE],
format: $code,
comment: FALSE,
endOfNode: FALSE,
deltaLevel: 0,
propList: List.PutAssoc[key: $FromTiogaFile, val: $Yes, aList: NIL]
propList: NIL
];
nextS: IO.STREAM;
in: IO.STREAM;
nextRope: ROPE;
ropeList, newList: LIST OF ROPE;
writer: TiogaAccess.Writer ← TiogaAccess.Create[];
in ← FS.StreamOpen[fileName: filename, accessOptions: $read ! FS.Error => {result ← $Failure; msg ← error.explanation; CONTINUE}];
IF in = NIL THEN RETURN;
The following code assumes there are lines with one or more ropes per line. The first rope is of the form Interface.ProcName. Succeeding ropes up to the last rope in the line are assumed to be names of Implmentations which use that Interface.ProcName. The last rope is assumed to be the name of the Implementor of the Interface.ProcName.
UNTIL IO.EndOf[in] DO
newList ← ropeList ← NIL;
nextS ← IO.RIS[IO.GetLineRope[stream: in]]; -- get next rope from input
nextRope ← IO.GetTokenRope[nextS, IO.TokenProc].token; -- first token, boldface
PutRopeBold[Rope.Concat[nextRope, ":"]];
UNTIL IO.EndOf[nextS] DO ropeList ← CONS[IO.GetTokenRope[nextS, IO.TokenProc ! IO.EndOfStream => CONTINUE;].token, ropeList];
ENDLOOP;
IF ropeList#NIL THEN {
newList ← DReverse[ropeList]; -- destructive reverse
nextRope ← IF newList.rest=NIL THEN newList.first ELSE newList.rest.first; -- initialize in case never do following loop
FOR r: LIST OF ROPE ← newList, r.rest UNTIL r.rest=NIL DO -- do all but last rope
PutRope[Rope.Concat[" ", r.first]];
nextRope ← r.rest.first;
ENDLOOP;
IF doImpls THEN PutRopeItalic[Rope.Concat[" ", nextRope]] -- last rope is Implementor, italics
ELSE PutRope[Rope.Concat[" ", nextRope]]; -- last rope is not Implementor
EndNode[delta: 0, format: $code];
};
ENDLOOP;
TiogaAccess.WriteFile[writer, filename];
};
switchChar: CHAR = '-;
gets: ROPENIL;
outputStream: IO.STREAMNIL;
symTab: SymTab.Ref ← SymTab.Create[997, TRUE];
scanner: Scanner ← CreateScanner[];
c: IO.STREAMIO.RIS[cmd.commandLine];
outputName: ROPE ← GetCmdToken[c];
IF outputName=NIL OR Rope.Equal[outputName, ""] THEN RETURN[$Failure, docRope];
doImpls ← reformat ← FALSE;
WHILE Rope.Fetch[base: outputName, index: 0]=switchChar DO
FOR index: INT IN [1..Rope.Length[outputName]) DO
SELECT Rope.Fetch[base: outputName, index: index] FROM
'i => doImpls ← TRUE;
'f => reformat ← TRUE;
ENDCASE;
ENDLOOP;
outputName ← GetCmdToken[c];
ENDLOOP;
gets ← GetCmdToken[c];
IF NOT gets.Equal["←"] THEN RETURN[$Failure, docRope];
outputStream ← FS.StreamOpen[fileName: outputName, accessOptions: $create, keep: 2 ! FS.Error => {result ← $Failure; msg ← error.explanation; CONTINUE}];
IF outputStream = NIL THEN RETURN;
FOR stem: ROPE ← GetCmdToken[c], GetCmdToken[c] UNTIL stem.Length = 0 DO
pattern: ROPEIF Rope.Find[stem, ".bcd", 0, FALSE] < 0 THEN Rope.Concat[stem, ".bcd"] ELSE stem;
ForEachFile: PROC [fullFName: ROPE] RETURNS [continue: BOOLEANTRUE] ~ {
bcdDate: BasicTime.GMT ~ FS.FileInfo[name: fullFName, remoteCheck: FALSE].created;
base: ROPE;
cp: FS.ComponentPositions;
useName: ROPE ← Rope.Concat[base, ".usingList"];
[fullFName, cp] ← FS.ExpandName[fullFName];
base ← fullFName.Substr[cp.base.start, cp.base.length];
useName ← FindNewer[Rope.Concat[base, ".usingList"], bcdDate];
IF useName=NIL THEN {
result ← CommandTool.DoCommand[Rope.Concat["UsingList ", fullFName], cmd];
useName ← Rope.Concat[base, ".usingList"];
}
ELSE {
cmd.out.PutF["Using old %g\n", IO.rope[useName]];
result ← NIL;
};
IF result = $Failure THEN NULL
ELSE {
stream: IO.STREAMNIL;
stream ← FS.StreamOpen[useName ! FS.Error => {cmd.out.PutRope[error.explanation]; cmd.out.PutChar['\n]; CONTINUE}];
IF stream # NIL THEN {
SetScanner[scanner, stream];
RecordDependencies[scanner, base, cmd, symTab];
stream.Close;
};
};
};
IF Rope.Find[pattern, "*"] >= 0 THEN FS.EnumerateForNames[pattern, ForEachFile ! FS.Error => {msg ← error.explanation; result ← $Failure; CONTINUE}]
ELSE [] ← ForEachFile[pattern ! FS.Error => {msg ← error.explanation; result ← $Failure; CONTINUE}];
IF result = $Failure THEN RETURN;
ENDLOOP;
WriteResults[outputStream, symTab, cmd];
outputStream.Close;
result ← CommandTool.DoCommand[Rope.Concat["ReadIndent ", outputName], cmd];
IF reformat THEN Reformat[outputName];
cmd.out.PutF["%g written.\n", IO.rope[outputName]];
};
reformat: BOOLFALSE;
doImpls: BOOLFALSE;
docRope: ROPE ~ "Create listing of procedure-level dependencies from bcds ([switches] <outputFile> ← <list of bcd patterns>)\n";
Commander.Register["UsingDependencies", UsingDependenciesCommand, docRope];
END.