DefDWIMImpl.mesa
Copyright Ó 1992 by Xerox Corporation. All rights reserved.
Mike Spreitzer March 1, 1987 5:45:39 pm PST
Last tweaked by Mike Spreitzer on September 10, 1992 10:36 am PDT
DIRECTORY Atom, BackStop, Basics, DefDWIM, DocCache, FileDWIM, FS, IO, JaMAndStyleAnalyses, Menus, MesaAnalyses, RefTab, RegularExpression, Rope, RopeHash, RopeList, RopeReader, SimpleFeedback, SymTab, TDJaMScanner, TDLexing, TEditDocument, TEditDocumentPrivate, TEditInput, TEditOps, TEditProfile, TEditRefresh, TEditSelection, TEditSelectionOps, TextFind, TextNode, TiogaFind, TiogaMenuOps, TiogaOps, TiogaOpsDefs, TiogaRopes, TiogaStreams, UserProfile, ViewerClasses, ViewerForkers, VM;
DefDWIMImpl:
CEDAR
PROGRAM
IMPORTS Atom, BackStop, DocCache, FileDWIM, FS, IO, JaMAndStyleAnalyses, MesaAnalyses, RefTab, RegularExpression, Rope, RopeHash, RopeList, RopeReader, SimpleFeedback, SymTab, TDJaMScanner, TDLexing, TEditDocumentPrivate, TEditInput, TEditOps, TEditProfile, TEditRefresh, TEditSelection, TextFind, TextNode, TiogaFind, TiogaMenuOps, TiogaOps, TiogaRopes, TiogaStreams, UserProfile, ViewerForkers, VM
EXPORTS DefDWIM
SHARES ViewerClasses --in order to change existing Def button, which doesn't decode control
=
BEGIN OPEN TDLexing, DefDWIM;
Stuff
LORA: TYPE = LIST OF REF ANY;
ROPEList: TYPE = LIST OF ROPE;
RefTextNode: TYPE = TextNode.RefTextNode;
MesaAnalysis: TYPE = MesaAnalyses.MesaAnalysis;
Viewer: TYPE = ViewerClasses.Viewer;
Kernel
RegistrationList: TYPE = LIST OF Registration;
Registration: TYPE = RECORD [Test: LanguageTest, Find: Finder];
Failure: PUBLIC ERROR [where: Place, why: ROPE] = CODE;
registry: RegistrationList ¬ NIL;
FindDef:
PUBLIC
PROC [subjectRope:
ROPE, from: Place, deep, verbose:
BOOL, interrupt:
REF
BOOL ¬
NIL]
RETURNS [fp: FoundPlace]
--Finder-- = {
FOR rl: RegistrationList ¬ registry, rl.rest
WHILE rl #
NIL
DO
IF rl.first.Test[from] THEN RETURN [rl.first.Find[subjectRope, from, deep, verbose, interrupt]];
ENDLOOP;
RETURN [DefaultFinder[subjectRope, from, deep, verbose, interrupt]];
};
Register:
PUBLIC
PROC [languageTest: LanguageTest, finder: Finder] = {
registry ¬ CONS[[languageTest, finder], registry];
};
Msg:
PROC [msgType: SimpleFeedback.MsgType, r1, r2, r3, r4, r5:
ROPE ¬
NIL] = {
msg: ROPE = Rope.Cat[r1, r2, r3, r4, r5];
SimpleFeedback.Append[$DefDWIM, msgType, $FYI, msg];
};
Some particular languages
DefaultFinder:
PROC [subjectRope:
ROPE, from: Place, deep, verbose:
BOOL, interrupt:
REF
BOOL ¬
NIL]
RETURNS [fp: FoundPlace]
--Finder-- = {
quick: TextFind.Target ~ TextFind.TargetFromRope[rope: Rope.Concat[subjectRope, ":"], pattern: FALSE];
textRoot: RefTextNode = TextNode.Root[from.loc.node];
start: INT = 0;
startLoc: Location = TextNode.LocRelative[[textRoot, 0], start];
before, after: INT ¬ 0;
foundLoc, nameEnd: Location;
foundNode: TextNode.Ref ¬ NIL;
TryFwd:
PROC [fromLoc: Location]
RETURNS [found:
BOOL] = {
[node: foundNode, matchStart: before, matchEnd: after] ¬ TiogaFind.Search[direction: forward, target: quick, loc1: fromLoc, loc2: TextNode.LastLocWithin[textRoot]--short by one, but probably won't matter--, match: word, interrupt: interrupt];
RETURN [foundNode#NIL]};
TryBkwd:
PROC
RETURNS [found:
BOOL] = {
[node: foundNode, matchStart: before, matchEnd: after] ¬ TiogaFind.Search[direction: backward, target: quick, loc1: startLoc, loc2: from.loc, match: word, interrupt: interrupt];
RETURN [foundNode#NIL]};
found:
BOOL ¬
SELECT from.searchOrder
FROM
fromStart => TryFwd[[textRoot, 0]],
fwdThenBkwd => TryFwd[from.loc] OR TryBkwd[],
bkwdThenFwd => TryBkwd[] OR TryFwd[from.loc],
ENDCASE => ERROR;
IF interrupt#NIL AND interrupt THEN Failure[from, Rope.Concat["interrupted search for ", subjectRope]];
IF
NOT found
THEN {
dot: INT ~ subjectRope.FindBackward["."];
subjLen: INT ~ subjectRope.Length;
IF dot
IN (0 .. subjLen-1)
THEN {
module: ROPE ~ subjectRope.Substr[len: dot];
sub: ROPE ~ subjectRope.Substr[start: dot+1];
next: Place ~ EnterCedarModule[from, module, verbose];
fp ¬ FindCedarDef[sub, next, deep, verbose, FALSE, NIL, MakeStack[], interrupt].cfr.fp;
RETURN};
};
IF found
THEN {
foundLoc ¬ TextNode.LocRelative[[foundNode, 0], before];
nameEnd ¬ TextNode.LocRelative[foundLoc, after-2 - before];
after ¬ TextNode.LocOffset[[textRoot, 0], [foundNode, after]];
fp ¬ [[from.fileName, foundLoc, fwdThenBkwd], nameEnd];
}
ELSE Failure[from, Rope.Concat["Couldn't find ", subjectRope]];
};
RecognizeJS:
PROC [place: Place]
RETURNS [is:
BOOL]
--LanguageTest-- = {
is ¬ WhatJSType[place] # Other;
};
JSType: TYPE = {JaM, Style, Other};
WhatJSType:
PROC [place: Place]
RETURNS [type: JSType] = {
cp: FS.ComponentPositions;
fullFName, ext: ROPE;
IF place.fileName.Length[] = 0 THEN RETURN [Other];
[fullFName, cp, ] ¬ FS.ExpandName[place.fileName];
ext ¬ fullFName.Substr[cp.ext.start, cp.ext.length];
type ¬
SELECT
TRUE
FROM
ext.Equal["JaM", FALSE] => JaM,
ext.Equal["Style", FALSE] => Style,
ENDCASE => Other;
};
JSFinder:
PROC [subjectRope:
ROPE, from: Place, deep, verbose:
BOOL, interrupt:
REF
BOOL ¬
NIL]
RETURNS [fp: FoundPlace]
--Finder-- = {
type: JSType = WhatJSType[from];
IF deep THEN subjectRope ¬ Rope.Concat["look.", subjectRope];
fp ¬ FindJSDef[subjectRope, from, verbose, NIL, MakeStack[], interrupt];
};
FindJSDef:
PROC [subjectRope:
ROPE, from: Place, verbose:
BOOL, searchedOpens: SymTab.Ref
--file name ¬
$T--, stack: RefTab.Ref
--Frame ¬
$T--, interrupt:
REF
BOOL ¬
NIL]
RETURNS [fp: FoundPlace]
--Finder-- = {
frame: Frame = NEW [FramePrivate ¬ [subjectRope, from]];
IF stack.Fetch[frame].found
THEN {
Failure[from, Rope.Concat["Recursion detected for ", subjectRope]];
};
IF NOT stack.Insert[frame, $T] THEN ERROR;
IF verbose THEN Msg[oneLiner, "Looking for ", subjectRope, " in ", from.fileName];
{ENABLE UNWIND => [] ¬ stack.Delete[frame];
quick: RegularExpression.Finder = RegularExpression.CreateFromRope[pattern: Rope.Cat["(", subjectRope, ")"], literal: TRUE, word: TRUE];
textRoot: RefTextNode = TextNode.Root[from.loc.node];
anal: JaMAndStyleAnalyses.JSAnalysis = JaMAndStyleAnalyses.GetAnalysis[textRoot, from.fileName, verbose];
start: INT = IF anal # NIL THEN anal.bodyStart ELSE 0;
startLoc: Location = TextNode.LocRelative[[textRoot, 0], start];
here: INT = TextNode.LocOffset[[textRoot, 0], from.loc];
before, after: INT ¬ 0;
foundLoc, nameEnd: Location;
TryFwd:
PROC [searchFrom:
INT]
RETURNS [found:
BOOL] = {
searchRope: ROPE = MF[TiogaRopes.RopeFromTioga[textRoot]];
found ¬ FALSE;
WHILE
NOT found
DO
[found: found, before: before, after: after] ¬ RegularExpression.SearchRope[finder: quick, rope: searchRope, start: searchFrom, interrupt: interrupt];
IF NOT found THEN EXIT;
IF NOT JaMOKAround[searchRope, subjectRope, before, after] THEN {searchFrom ¬ after; found ¬ FALSE};
ENDLOOP;
};
TryBkwd:
PROC
RETURNS [found:
BOOL] = {
searchRope: ROPE = MF[TiogaRopes.RopeFromTioga[textRoot]];
searchLength: INT ¬ here - start;
found ¬ FALSE;
WHILE
NOT found
DO
[found: found, before: before, after: after] ¬ RegularExpression.SearchRopeBackwards[finder: quick, rope: searchRope, start: start, len: searchLength, interrupt: interrupt];
IF NOT found THEN EXIT;
IF NOT JaMOKAround[searchRope, subjectRope, before, after] THEN {searchLength ¬ before - start; found ¬ FALSE};
ENDLOOP;
};
found:
BOOL ¬
SELECT from.searchOrder
FROM
fromStart => TryFwd[0],
fwdThenBkwd => TryFwd[here] OR TryBkwd[],
bkwdThenFwd => TryBkwd[] OR TryFwd[here],
ENDCASE => ERROR;
IF interrupt#NIL AND interrupt THEN Failure[from, Rope.Concat["interrupted search for ", subjectRope]];
IF found
THEN {
foundLoc ¬ TextNode.LocRelative[[textRoot, 0], before+1];
nameEnd ¬ TextNode.LocRelative[foundLoc, after-2 - (before+1)];
after ¬ TextNode.LocOffset[[textRoot, 0], nameEnd]+1;
fp ¬ [[from.fileName, foundLoc, fwdThenBkwd], nameEnd];
}
ELSE {
Try:
PROC [files: ROPEList, ext:
ROPE]
RETURNS [found:
BOOL] = {
FOR files ¬ files, files.rest
WHILE files #
NIL
DO
fileName: ROPE = files.first.Concat[ext];
IF interrupt#NIL AND interrupt THEN Failure[from, Rope.Concat["interrupted search for ", subjectRope]];
IF searchedOpens = NIL THEN searchedOpens ¬ SymTab.Create[case: TRUE];
found ¬ TRUE;
IF searchedOpens.Fetch[fileName].found THEN found ¬ FALSE
ELSE {
IF NOT searchedOpens.Insert[fileName, $T] THEN ERROR;
fp ¬ FindJSDef[subjectRope, JSModuleBody[from, fileName, verbose, stack, interrupt].where, verbose, searchedOpens, stack, interrupt !Failure => {found ¬ FALSE; CONTINUE}];
};
IF found THEN RETURN;
ENDLOOP;
found ¬ FALSE;
};
IF anal #
NIL
AND (Try[anal.runs, ".JaM"]
OR Try[anal.attachs, ".Style"])
THEN NULL
ELSE Failure[from, Rope.Concat["Couldn't find ", subjectRope]];
};
};
IF NOT stack.Delete[frame] THEN ERROR;
};
JSModuleBody:
PROC [from: Place, filename:
ROPE, verbose:
BOOL, stack: RefTab.Ref
--Frame ¬
$T--, interrupt:
REF
BOOL]
RETURNS [fp: FoundPlace] = {
ans: FileDWIM.Answer = FileDWIM.ResolveHint[filename, from.fileName, FALSE];
IF ans.fullFileName = NIL THEN ERROR Failure[from, Rope.Concat["couldn't find file ", filename]];
{next: Place = [ans.fullFileName, [GetDoc[from, ans.fullFileName], 0], fromStart];
fp ¬ [next, next.loc];
}};
JaMOKAround:
PROC [searchRope, target:
ROPE, before, after:
INT]
RETURNS [ok:
BOOL] = {
Token: TYPE = TDJaMScanner.Token;
reader: RopeReader.Ref = RopeReader.Create[];
Match:
PROC [left: {leftSquare, leftCurly}]
RETURNS [ok:
BOOL] = {
DO
token: Token = TDJaMScanner.GetToken[reader];
SELECT token.type
FROM
nil => RETURN [FALSE--because EOF--];
name => {
a: ATOM = NARROW[TDJaMScanner.ParseToken[token, searchRope]];
SELECT a
FROM
leftSquare => IF NOT Match[leftSquare] THEN RETURN [FALSE];
rightSquare => RETURN [left = leftSquare];
ENDCASE => NULL;
};
string, int, real => NULL;
lbrace => IF NOT Match[leftCurly] THEN RETURN [FALSE];
rbrace => RETURN [left = leftCurly];
comment => NULL;
ENDCASE => ERROR;
ENDLOOP;
};
stack: LORA ¬ LIST[target];
IF after >= searchRope.Length[] THEN RETURN [FALSE];
reader.SetPosition[searchRope, after];
DO
token: Token = TDJaMScanner.GetToken[reader];
SELECT token.type
FROM
nil => RETURN [FALSE--because EOF--];
name => {
a: ATOM = NARROW[TDJaMScanner.ParseToken[token, searchRope]];
SELECT a
FROM
dotCvx, $bp => NULL;
dotDef, $StyleParam => RETURN [stack # NIL AND stack.rest # NIL AND stack.rest.rest = NIL];
slashDef, $StyleRule, $ScreenRule, $PrintRule => RETURN [stack # NIL AND stack.rest # NIL AND stack.rest.rest # NIL AND stack.rest.rest.rest = NIL];
slashXdef => RETURN [stack # NIL AND stack.rest # NIL AND stack.rest.rest # NIL AND stack.rest.rest.rest = NIL];
leftSquare => IF Match[leftSquare] THEN stack ¬ CONS[$square, stack] ELSE RETURN [FALSE];
dotTrue => stack ¬ CONS[a, stack];
ENDCASE => RETURN [FALSE];
};
string, int, real => stack ¬ CONS[TDJaMScanner.ParseToken[token, searchRope], stack];
lbrace => IF Match[leftCurly] THEN stack ¬ CONS[$curly, stack] ELSE RETURN [FALSE];
rbrace => RETURN [FALSE];
comment => NULL;
ENDCASE => ERROR;
ENDLOOP;
};
dotDef: ATOM = Atom.MakeAtom[".def"];
dotCvx: ATOM = Atom.MakeAtom[".cvx"];
dotTrue: ATOM = Atom.MakeAtom[".true"];
slashDef: ATOM = Atom.MakeAtom["/def"];
slashXdef: ATOM = Atom.MakeAtom["/xdef"];
leftSquare: ATOM = Atom.MakeAtom["["];
rightSquare: ATOM = Atom.MakeAtom["]"];
RecognizeLisp:
PROC [place: Place]
RETURNS [is:
BOOL] = {
textRoot: RefTextNode = TextNode.Root[place.loc.node];
asRope: ROPE = MF[TiogaRopes.RopeFromTioga[node: textRoot, skipCommentNode: TRUE]];
length: INT = asRope.Length[];
in: IO.STREAM = IO.RIS[asRope];
[] ¬ in.SkipWhitespace[];
IF in.EndOf[] THEN RETURN [FALSE];
{firstNonwhite: CHAR = in.GetChar[];
in.Close[];
IF firstNonwhite # '( THEN RETURN [FALSE];
FOR i:
INT ¬ length-1, i-1
WHILE i > 0
DO
SELECT asRope.Fetch[i]
FROM
IN ['\000 .. IO.SP] => NULL;
') => RETURN [TRUE];
ENDCASE => RETURN [FALSE];
ENDLOOP;
RETURN [FALSE];
}};
LispFinder:
PROC [subjectRope:
ROPE, from: Place, deep, verbose:
BOOL, interrupt:
REF
BOOL ¬
NIL]
RETURNS [fp: FoundPlace]
--Finder-- = {
quick: TextFind.Target ¬ TextFind.TargetFromRope[rope: Rope.Concat["(defun ", subjectRope], pattern: FALSE];
textRoot: RefTextNode = TextNode.Root[from.loc.node];
start: INT = 0;
startLoc: Location = TextNode.LocRelative[[textRoot, 0], start];
before, after: INT ¬ 0;
foundLoc, nameEnd: Location;
foundNode: TextNode.Ref ¬ NIL;
eod: Location ~ TextNode.LastLocWithin[textRoot];
TryFwd:
PROC [fromLoc: Location]
RETURNS [found:
BOOL] = {
[node: foundNode, matchStart: before, matchEnd: after] ¬ TiogaFind.Search[direction: forward, target: quick, loc1: from.loc, loc2: eod, match: word, interrupt: interrupt];
RETURN [foundNode#NIL]};
TryBkwd:
PROC
RETURNS [found:
BOOL] = {
[node: foundNode, matchStart: before, matchEnd: after] ¬ TiogaFind.Search[direction: backward, target: quick, loc1: startLoc, loc2: from.loc, match: word, interrupt: interrupt];
RETURN [foundNode#NIL]};
found:
BOOL ¬
SELECT from.searchOrder
FROM
fromStart => TryFwd[startLoc],
fwdThenBkwd => TryFwd[from.loc] OR TryBkwd[],
bkwdThenFwd => TryBkwd[] OR TryFwd[from.loc],
ENDCASE => ERROR;
IF interrupt#NIL AND interrupt THEN Failure[from, Rope.Concat["interrupted search for ", subjectRope]];
IF found
THEN {
foundLoc ¬ TextNode.LocRelative[[foundNode, 0], before+7];
nameEnd ¬ TextNode.LocRelative[foundLoc, after-1 - (before+7)];
after ¬ TextNode.LocOffset[[textRoot, 0], [foundNode, after]];
fp ¬ [[from.fileName, foundLoc, fwdThenBkwd], nameEnd];
}
ELSE Failure[from, Rope.Concat["Couldn't find ", subjectRope]];
};
RecognizeM3:
PROC [place: Place]
RETURNS [
BOOL]
--LanguageTest-- = {
cp: FS.ComponentPositions;
fullFName, ext: ROPE;
c0, c1: CHAR;
IF place.fileName.Length[] = 0 THEN RETURN [FALSE];
[fullFName, cp, ] ¬ FS.ExpandName[place.fileName];
ext ¬ fullFName.Substr[cp.ext.start, cp.ext.length];
IF ext.Length[] # 2 THEN RETURN [FALSE];
c0 ¬ ext.Fetch[0];
IF c0#'i AND c0#'m THEN RETURN [FALSE];
c1 ¬ ext.Fetch[1];
IF c1#'3 AND c1#'g THEN RETURN [FALSE];
RETURN [TRUE]};
M3Finder:
PROC [subjectRope:
ROPE, from: Place, deep, verbose:
BOOL, interrupt:
REF
BOOL ¬
NIL]
RETURNS [fp: FoundPlace]
--Finder-- = {
dot: INT ~ subjectRope.FindBackward["."];
subjLen: INT ~ subjectRope.Length;
IF dot
IN (0 .. subjLen-1)
THEN {
module: ROPE ~ subjectRope.Substr[len: dot];
sub: ROPE ~ subjectRope.Substr[start: dot+1];
ans: FileDWIM.Answer = FileDWIM.ResolveHint[module, from.fileName];
IF ans.fullFileName#
NIL
THEN {
root: TextNode.Ref ~ GetDoc[from, ans.fullFileName];
loc: Location ~ TextNode.LocRelative[[root, 0], 0];
RETURN M3Finder[sub, [ans.fullFileName, loc, fwdThenBkwd], deep, verbose, interrupt]};
ERROR Failure[from, IO.PutFR1["can't find a file for %g", [rope[subjectRope]] ]]}
ELSE {
target: ROPE ~ subjectRope;
quick: TextFind.Target ~ TextFind.TargetFromRope[rope: Rope.Concat[subjectRope, ":"], pattern: FALSE];
textRoot: RefTextNode = TextNode.Root[from.loc.node];
start: INT = 0;
startLoc: Location = TextNode.LocRelative[[textRoot, 0], start];
before, after: INT ¬ 0;
foundLoc, nameEnd: Location;
foundNode: TextNode.Ref ¬ NIL;
TryFwd:
PROC [fromLoc: Location]
RETURNS [found:
BOOL] = {
[node: foundNode, matchStart: before, matchEnd: after] ¬ TiogaFind.Search[direction: forward, target: quick, loc1: fromLoc, loc2: TextNode.LastLocWithin[textRoot]--short by one, but probably won't matter--, match: word, interrupt: interrupt];
RETURN [foundNode#NIL]};
TryBkwd:
PROC
RETURNS [found:
BOOL] = {
[node: foundNode, matchStart: before, matchEnd: after] ¬ TiogaFind.Search[direction: backward, target: quick, loc1: startLoc, loc2: from.loc, match: word, interrupt: interrupt];
RETURN [foundNode#NIL]};
found:
BOOL ¬
SELECT from.searchOrder
FROM
fromStart => TryFwd[[textRoot, 0]],
fwdThenBkwd => TryFwd[from.loc] OR TryBkwd[],
bkwdThenFwd => TryBkwd[] OR TryFwd[from.loc],
ENDCASE => ERROR;
IF interrupt#NIL AND interrupt THEN Failure[from, Rope.Concat["interrupted search for ", subjectRope]];
IF found
THEN {
foundLoc ¬ TextNode.LocRelative[[foundNode, 0], before];
nameEnd ¬ TextNode.LocRelative[foundLoc, after-2 - before];
after ¬ TextNode.LocOffset[[textRoot, 0], [foundNode, after]];
fp ¬ [[from.fileName, foundLoc, fwdThenBkwd], nameEnd];
}
ELSE {
xStart: INT ~ 0;
xHere: INT ~ TextNode.LocOffset[[textRoot, 0], from.loc, 1, excludeComments];
targLen: INT = target.Length[];
vslow: RegularExpression.Finder = RegularExpression.CreateFromRope[Rope.Concat[target, "[ \n\t]**(',[ \n\t]**[a..zA..Z][a..zA..Z0..9←]**[ \n\t]**)**(':|=|'<':)"]];
pslow: RegularExpression.Finder = RegularExpression.CreateFromRope[Rope.Concat[target, "[ \n\t]**('(|';)" ]];
searchRope: ROPE ~ MF[TiogaRopes.RopeFromTioga[node: textRoot, skipCommentNode: excludeComments]];
TryFwd:
PROC [slow: RegularExpression.Finder, searchFrom:
INT]
RETURNS [found:
BOOL] = {
DO
[found: found, before: before, after: after] ¬ RegularExpression.SearchRope[finder: slow, rope: searchRope, start: searchFrom, interrupt: interrupt];
IF NOT found THEN EXIT;
IF NOT (IF slow=vslow THEN M3OKBefore ELSE ProcOrExnBefore)[searchRope, before, after] THEN {searchFrom ¬ after; found ¬ FALSE} ELSE RETURN [TRUE];
ENDLOOP;
};
TryBkwd:
PROC [slow: RegularExpression.Finder]
RETURNS [found:
BOOL] = {
searchLength: INT ¬ xHere - xStart;
DO
[found: found, before: before, after: after] ¬ RegularExpression.SearchRopeBackwards[finder: slow, rope: searchRope, start: xStart, len: searchLength, interrupt: interrupt];
IF NOT found THEN EXIT;
IF NOT (IF slow=vslow THEN M3OKBefore ELSE ProcOrExnBefore)[searchRope, before, after] THEN {searchLength ¬ before - xStart; found ¬ FALSE} ELSE RETURN [TRUE];
ENDLOOP;
};
IF verbose THEN Msg[begin, "Looking hard for ", target, " in ", from.fileName];
found ¬
SELECT from.searchOrder
FROM
fromStart => TryFwd[pslow, 0] OR TryFwd[vslow, 0],
fwdThenBkwd => TryFwd[pslow, xHere] OR TryFwd[vslow, xHere] OR TryBkwd[pslow] OR TryBkwd[vslow],
bkwdThenFwd => TryBkwd[pslow] OR TryBkwd[vslow] OR TryFwd[pslow, xHere] OR TryFwd[vslow, xHere],
ENDCASE => ERROR;
IF interrupt#NIL AND interrupt THEN Failure[from, Rope.Concat["interrupted search for ", target]];
IF verbose THEN Msg[end, " ."];
IF found
THEN {
foundLoc ¬ TextNode.LocRelative[[textRoot, 0], before, 1, excludeComments];
nameEnd ¬ TextNode.LocRelative[foundLoc, targLen-1];
fp ¬ [[from.fileName, foundLoc, fwdThenBkwd], nameEnd];
}
ELSE Failure[from, Rope.Concat["Couldn't find ", subjectRope]];
};
};
};
ProcOrExnBefore:
PROC [searchRope:
ROPE, before, after:
INT]
RETURNS [ok:
BOOL] = {
x: INT ~ before;
EatWhite:
PROC
RETURNS [i:
INT] ~ {
i ¬ before;
WHILE i>0
DO
SELECT searchRope.Fetch[i-1]
FROM
IN [0C .. ' ] => i ¬ i-1;
ENDCASE => EXIT;
ENDLOOP;
RETURN};
GetId:
PROC
RETURNS [id:
ROPE] ~ {
end: INT ~ before;
WHILE before>0
DO
char: CHAR ~ searchRope.Fetch[before-1];
SELECT char
FROM
IN['a..'z], IN['A..'Z], IN['0..'9], '← => before ¬ before-1;
ENDCASE => EXIT;
ENDLOOP;
RETURN [searchRope.Substr[start: before, len: end-before]]};
EndsIn:
PROC [e:
ROPE]
RETURNS [ok:
BOOL] ~ {
got: ROPE ~ GetId[];
RETURN [e.Equal[got]]};
got: ROPE;
before ¬ EatWhite[];
IF before=x THEN RETURN [FALSE];
got ¬ GetId[];
RETURN [got.Equal["PROCEDURE"] OR got.Equal["EXCEPTION"]]};
M3OKBefore:
PROC [searchRope:
ROPE, before, after:
INT]
RETURNS [ok:
BOOL] = {
When match ends in ":=", accepts, in reverse:
("VAR"|"VALUE"|"READONLY" white)|("PROCEDURE" white* id white* "(" ) (white* id white* ",")* white*
Otherwise, accepts, in reverse:
white|","
hard: BOOL ~ after>0 AND after<searchRope.Length[] AND searchRope.Fetch[after]='= AND searchRope.Fetch[after-1]=': ;
state: {seekComma, seekId, seekProcname} ¬ seekComma;
EatWhite:
PROC
RETURNS [i:
INT] ~ {
i ¬ before;
WHILE i>0
DO
SELECT searchRope.Fetch[i-1]
FROM
IN [0C .. ' ] => i ¬ i-1;
ENDCASE => EXIT;
ENDLOOP;
RETURN};
GetId:
PROC
RETURNS [id:
ROPE] ~ {
end: INT ~ before;
WHILE before>0
DO
char: CHAR ~ searchRope.Fetch[before-1];
SELECT char
FROM
IN['a..'z], IN['A..'Z], IN['0..'9], '← => before ¬ before-1;
ENDCASE => EXIT;
ENDLOOP;
RETURN [searchRope.Substr[start: before, len: end-before]]};
EndsIn:
PROC [e:
ROPE]
RETURNS [ok:
BOOL] ~ {
got: ROPE ~ GetId[];
RETURN [e.Equal[got]]};
IF
NOT hard
THEN {
char: CHAR;
IF before<=0 THEN RETURN [TRUE];
char ¬ searchRope.Fetch[before-1];
SELECT char
FROM
IN [0C .. ' ], ', => RETURN [TRUE];
ENDCASE => RETURN [FALSE]};
WHILE (before ¬ EatWhite[]) > 0
DO
char: CHAR ¬ searchRope.Fetch[before ¬ before - 1];
SELECT state
FROM
seekComma =>
SELECT char
FROM
', => state ¬ seekId;
'( => state ¬ seekProcname;
'Y => RETURN EndsIn["READONL"];
'E => RETURN EndsIn["VALU"];
'R => RETURN EndsIn["VA"];
ENDCASE => RETURN [FALSE];
seekId =>
SELECT char
FROM
IN['a..'z], IN['A..'Z], IN['0..'9], '← => {[] ¬ GetId[]; state ¬ seekId};
ENDCASE => RETURN [FALSE];
seekProcname =>
SELECT char
FROM
IN['a..'z],
IN['A..'Z],
IN['0..'9], '← => {
[] ¬ GetId[];
before ¬ EatWhite[];
RETURN EndsIn["PROCEDURE"]};
ENDCASE => RETURN [FALSE];
ENDCASE => ERROR;
ENDLOOP;
RETURN [FALSE]};
CedarTree: TYPE = REF ANY --actually UNION [FieldSelection, ROPE]--;
FieldSelection: TYPE = REF FieldSelectionPrivate;
FieldSelectionPrivate:
TYPE =
RECORD [
record: CedarTree,
field: ROPE
];
Frame: TYPE = REF FramePrivate;
FramePrivate: TYPE = RECORD [tree: CedarTree, from: Place];
guessDIRECTORY: BOOL ¬ TRUE;
RecognizeCedar:
PROC [place: Place]
RETURNS [isCedar:
BOOL]
--LanguageTest-- = {
cp: FS.ComponentPositions;
fullFName, ext: ROPE;
IF place.fileName.Length[] = 0 THEN RETURN [FALSE];
[fullFName, cp, ] ¬ FS.ExpandName[place.fileName];
ext ¬ fullFName.Substr[cp.ext.start, cp.ext.length];
isCedar ¬ ext.Equal["mesa", FALSE] OR ext.Equal["cedar", FALSE];
};
CedarFinder:
PROC [subjectRope:
ROPE, from: Place, deep, verbose:
BOOL, interrupt:
REF
BOOL ¬
NIL]
RETURNS [fp: FoundPlace]
--Finder-- = {
in: IO.STREAM = IO.RIS[subjectRope];
p: Lexer = MakeLexer[in, NIL, excludeComments];
{ENABLE IO.Error => IF stream = in THEN Failure[noplace, IF ec = SyntaxError THEN "selection not of form ID(\".\"ID)*" ELSE "IO.Error parsing selection"];
t1: Token = GetToken[p];
IF t1.kind # tokenID THEN Failure[noplace, "selection not of form ID(\".\"ID)*"];
{tree: CedarTree = GetTree[p, t1];
in.Close[];
IF p.tokenStack = NIL OR p.tokenStack.rest # NIL OR p.tokenStack.first.kind # tokenEOF THEN Failure[noplace, "selection not of form ID(\".\"ID)*"];
fp ¬ FindCedarDef[tree, from, deep, verbose, TRUE, NIL, MakeStack[], interrupt].cfr.fp;
}}};
excludeComments:
BOOL ¬
TRUE;
Controls whether the Cedar finder ignores comment nodes.
CedarFindResult:
TYPE ~
RECORD [
fp: FoundPlace,
recTypeLexer: Lexer¬NIL --#NIL => positioned between ("RECORD" or "=>") and "["--,
l0: Location--in fp's doc--,
i0: INT--index at l0--
];
fieldNameFollowers: ROPEList ~ LIST[":", ",", "=>", "("];
typeBinders: ROPEList ~ LIST["~", "="];
FindCedarDef:
PROC [tree: CedarTree, from: Place, deep, verbose, leftmost:
BOOL, searchedOpens: SymTab.Ref
--module name ¬
$T--, stack: RefTab.Ref
--Frame ¬
$T--, interrupt:
REF
BOOL ¬
NIL]
RETURNS [cfr: CedarFindResult] = {
If not leftmost, we never guess it might be a missing DIRECTORY entry.
An OPENed module without a USING list is recorded in searchedOpens, which may be NIL.
frame: Frame = NEW [FramePrivate ¬ [tree, from]];
IF stack.Fetch[frame].found
THEN {
Failure[from, Rope.Concat["Recursion detected for ", FmtTree[tree]]];
};
IF NOT stack.Insert[frame, $T] THEN ERROR;
{ENABLE UNWIND => [] ¬ stack.Delete[frame];
FinishDeep:
PROC [p: Lexer, cfr: CedarFindResult]
RETURNS [CedarFindResult] ~ {
p is positioned after the colon or Rightarrow.
ENABLE
IO.Error =>
IF stream=p.in
THEN Failure[
cfr.fp.where,
IF ec=SyntaxError THEN "Cedar Syntax Error" ELSE "IO.Error"];
toke: Token;
IF cfr.recTypeLexer#NIL THEN ERROR;
SkipMumble[p];
toke ¬ GetToken[p];
IF toke.kind = tokenID
THEN {
IF
NOT ReservedWord[toke.rope]
THEN {
defTree: CedarTree = GetTree[p, toke];
IF (
WITH defTree
SELECT
FROM
rope: ROPE => NOT PredefinedType[rope],
ENDCASE => TRUE)
THEN RETURN FindCedarDef[defTree, cfr.fp.where, TRUE, verbose, TRUE, NIL, stack, interrupt];
}
ELSE
IF toke.rope.Equal["TYPE"]
THEN {
toke2: Token = GetToken[p];
IF toke2.kind = tokenSINGLE
AND RopeList.Memb[list: typeBinders, r: toke2.rope]
THEN {
SkipMumble[p];
{toke3: Token = GetToken[p];
IF toke3.kind#tokenID THEN NULL
ELSE
IF
NOT ReservedWord[toke3.rope]
THEN {
defTree: CedarTree = GetTree[p, toke3];
IF (
WITH defTree
SELECT
FROM
rope: ROPE => NOT PredefinedType[rope],
ENDCASE => TRUE)
THEN RETURN FindCedarDef[defTree, cfr.fp.where, deep, verbose, TRUE, NIL, stack, interrupt];
}
ELSE
IF toke3.rope.Equal["RECORD"]
THEN {
cfr.recTypeLexer ¬ p;
RETURN [cfr]};
}};
};
};
ReturnToken[p, toke];
RETURN [cfr]};
WITH tree
SELECT
FROM
fs: FieldSelection => {
next: CedarFindResult = FindCedarDef[fs.record, from, TRUE, verbose, leftmost, searchedOpens, stack, interrupt];
nextCopy: CedarFindResult ~ next;
l: Lexer ~ next.recTypeLexer;
lCopy: Lexer ~ l;
IF l#
NIL
THEN {
state: {fieldName, fieldType} ¬ fieldName;
toke: Token ¬ GetToken[l];
matched: BOOL ¬ FALSE;
first: BOOL ¬ TRUE;
ThisToken:
PROC [rt:
BOOL] ~ {
nameBegin: TextNode.Location ~ TextNode.LocRelative[next.l0, toke.before-next.i0, 1, excludeComments];
nameEnd: TextNode.Location ~ TextNode.LocRelative[nameBegin, toke.after-1-toke.before, 1, excludeComments];
cfr ¬ [fp: [where: [next.fp.where.fileName, nameBegin, fwdThenBkwd], nameEnd: nameEnd], recTypeLexer: IF rt THEN l ELSE NIL, l0: nameEnd, i0: toke.after];
matched ¬ TRUE;
RETURN};
IF NOT toke.rope.Equal["["] THEN GOTO Not;
DO
thisFirst: BOOL ~ first;
bracketDepth: INT ¬ 0;
first ¬ FALSE;
toke ¬ GetToken[l];
IF state=fieldType THEN matched ¬ FALSE;
IF toke.kind = tokenEOF THEN GOTO Not;
IF state=fieldName AND toke.kind=tokenID AND toke.rope.Equal[fs.field] AND RopeList.Memb[fieldNameFollowers, PeekToken[l].rope] THEN ThisToken[TRUE];
IF state=fieldType
OR thisFirst
THEN {
--with another layer of looping, we could handle 1,anonymous,element records in their complete generality.
IF toke.kind=tokenID
AND toke.rope.Equal["SELECT"]
THEN {
toke ¬ GetToken[l];
IF toke.kind=tokenID AND toke.rope.Equal[fs.field] THEN ThisToken[FALSE]
ELSE {
UNTIL toke.kind=tokenEOF OR toke.kind=tokenID AND toke.rope.Equal["FROM"] DO toke ¬ GetToken[l] ENDLOOP;
state ¬ fieldName;
LOOP}
}
ELSE cfr.recTypeLexer ¬ NIL;
};
DO
--skip to the first ',, ':, or "=>" outside balanced brackets
SELECT
TRUE
FROM
toke.kind=tokenSINGLE =>
SELECT toke.rope.Fetch[0]
FROM
'[, '( => bracketDepth ¬ bracketDepth + 1;
'), '] => {
bracketDepth ¬ bracketDepth - 1;
IF bracketDepth<0 THEN GOTO Not};
', => IF bracketDepth=0 THEN {state ¬ fieldName; EXIT};
': =>
IF bracketDepth=0
THEN {
IF state=fieldType OR matched AND cfr.recTypeLexer#NIL THEN GOTO Not;
state ¬ fieldType; EXIT};
ENDCASE => NULL;
toke.kind=tokenDOUBLE =>
SELECT
TRUE
FROM
toke.rope.Equal["=>"] =>
IF bracketDepth=0
THEN {
IF state=fieldType OR matched AND cfr.recTypeLexer=NIL THEN GOTO Not;
state ¬ fieldType; EXIT};
ENDCASE => NULL;
ENDCASE => NULL;
toke ¬ GetToken[l];
IF toke.kind = tokenEOF THEN GOTO Not;
ENDLOOP;
IF state=fieldType
AND matched
THEN {
IF deep AND cfr.recTypeLexer=NIL THEN cfr ¬ FinishDeep[l, cfr];
GOTO Dun};
ENDLOOP;
EXITS Not => stack ¬ stack};
cfr ¬ FindCedarDef[fs.field, next.fp.where, deep, verbose, FALSE, NIL, stack, interrupt];
};
target:
ROPE => {
quick: TextFind.Target ¬ TextFind.TargetFromRope[rope: Rope.Concat[target, ":"], pattern: FALSE];
textRoot: RefTextNode = TextNode.Root[from.loc.node];
eod: Location ~ TextNode.LastLocWithin[textRoot];
T2RIndex: PROC [count: INT] RETURNS [x: INT] ~ {x ¬ IF excludeComments THEN TextNode.LocOffset[[textRoot, 0], TextNode.LocRelative[[textRoot, 0], count], 1, TRUE] ELSE count};
R2TIndex: PROC [count: INT] RETURNS [y: INT] ~ {y ¬ IF excludeComments THEN TextNode.LocOffset[[textRoot, 0], TextNode.LocRelative[[textRoot, 0], count, 1, TRUE]] ELSE count};
ma: MesaAnalysis = MesaAnalyses.GetAnalysis[textRoot, from.fileName, verbose];
start: INT = IF ma # NIL THEN ma.bodyStart ELSE 0;
startLoc: Location = TextNode.LocRelative[[textRoot, 0], start];
before, after: INT ¬ 0;
found: BOOL;
foundLoc, nameEnd: Location;
IF verbose THEN Msg[begin, "Looking for ", target, " in ", from.fileName];
{foundNode: TextNode.Ref ¬ NIL;
TryFwd:
PROC [fromLoc: Location]
RETURNS [found:
BOOL] = {
[node: foundNode, matchStart: before, matchEnd: after] ¬ TiogaFind.Search[direction: forward, target: quick, loc1: from.loc, loc2: eod, match: word, checkComment: excludeComments, interrupt: interrupt]--returns a half-open interval--;
RETURN [foundNode#NIL]};
TryBkwd:
PROC
RETURNS [found:
BOOL] = {
[node: foundNode, matchStart: before, matchEnd: after] ¬ TiogaFind.Search[direction: backward, target: quick, loc1: startLoc, loc2: from.loc, match: word, checkComment: excludeComments, interrupt: interrupt]--returns a half-open interval--;
RETURN [foundNode#NIL]};
found ¬
SELECT from.searchOrder
FROM
fromStart => TryFwd[[textRoot, 0]],
fwdThenBkwd => TryFwd[from.loc] OR TryBkwd[],
bkwdThenFwd => TryBkwd[] OR TryFwd[from.loc],
ENDCASE => ERROR;
IF interrupt#NIL AND interrupt THEN Failure[from, Rope.Concat["interrupted search for ", target]];
IF verbose THEN Msg[end, " ."];
IF found
THEN {
foundLoc ¬ TextNode.LocRelative[[foundNode, 0], before];
nameEnd ¬ TextNode.LocRelative[foundLoc, after-2--one for colon, one to close-- - before];
after ¬ TextNode.LocOffset[[textRoot, 0], [foundNode, after], 1, excludeComments];
}
};
IF (
NOT found)
AND ma #
NIL
THEN {
def: REF ANY = ma.globalDefs.Fetch[target].val;
IF def #
NIL
THEN {
RightHere:
PROC [ir: MesaAnalyses.InterfaceRecord]
RETURNS [cfr: CedarFindResult] ~ {
nameStart: TextNode.Location ~ TextNode.LocRelative[[textRoot, 0], ir.namePos.before];
nameEnd: TextNode.Location ~ TextNode.LocRelative[nameStart, ir.namePos.after-1 - ir.namePos.before];
cfr ¬ [
fp: [[from.fileName, nameStart, fwdThenBkwd], nameEnd],
recTypeLexer: NIL,
l0: nameEnd,
i0: ir.namePos.after-1];
RETURN};
cfr ¬
WITH def
SELECT
FROM
it: MesaAnalyses.InterfaceType => CedarModuleBody[from, it, verbose, stack, interrupt],
ir: MesaAnalyses.InterfaceRecord =>
IF deep
THEN CedarModuleBody[from, ir.type, verbose, stack, interrupt]
ELSE RightHere[ir],
ie: MesaAnalyses.InterfaceElement => FindCedarDef[ie.name, CedarModuleBody[from, ie.interface.type, verbose, stack, interrupt].cfr.fp.where, deep, verbose, FALSE, searchedOpens, stack, interrupt],
ENDCASE => ERROR;
GOTO Dun};
};
IF
NOT found
THEN {
here: INT = TextNode.LocOffset[[textRoot, 0], from.loc];
targLen: INT = target.Length[];
slow: RegularExpression.Finder = RegularExpression.CreateFromRope[target.Concat["[ \n\t]**(',[ \n\t]**[a..zA..Z][a..zA..Z0..9]**[ \n\t]**)**('([~'(')]++')[ \n\t]**|)':"]];
xStart: INT ~ T2RIndex[start];
xHere: INT ~ T2RIndex[here];
searchRope: ROPE ~ MF[TiogaRopes.RopeFromTioga[node: textRoot, skipCommentNode: excludeComments]];
TryFwd:
PROC [searchFrom:
INT]
RETURNS [found:
BOOL] = {
found ¬ FALSE;
WHILE
NOT found
DO
[found: found, before: before, after: after] ¬ RegularExpression.SearchRope[finder: slow, rope: searchRope, start: searchFrom, interrupt: interrupt];
IF NOT found THEN EXIT;
IF NOT CedarOKBefore[searchRope, before] THEN {searchFrom ¬ after; found ¬ FALSE};
ENDLOOP;
};
TryBkwd:
PROC
RETURNS [found:
BOOL] = {
searchLength: INT ¬ xHere - xStart;
found ¬ FALSE;
WHILE
NOT found
DO
[found: found, before: before, after: after] ¬ RegularExpression.SearchRopeBackwards[finder: slow, rope: searchRope, start: xStart, len: searchLength, interrupt: interrupt];
IF NOT found THEN EXIT;
IF NOT CedarOKBefore[searchRope, before] THEN {searchLength ¬ before - xStart; found ¬ FALSE};
ENDLOOP;
};
IF verbose THEN Msg[begin, "Looking hard for ", target, " in ", from.fileName];
found ¬
SELECT from.searchOrder
FROM
fromStart => TryFwd[0],
fwdThenBkwd => TryFwd[xHere] OR TryBkwd[],
bkwdThenFwd => TryBkwd[] OR TryFwd[xHere],
ENDCASE => ERROR;
IF interrupt#NIL AND interrupt THEN Failure[from, Rope.Concat["interrupted search for ", target]];
IF verbose THEN Msg[end, " ."];
IF found
THEN {
foundLoc ¬ TextNode.LocRelative[[textRoot, 0], before, 1, excludeComments];
nameEnd ¬ TextNode.LocRelative[foundLoc, targLen-1];
};
};
IF (
NOT found)
AND ma #
NIL
THEN {
FOR owus: MesaAnalyses.IRList ¬ ma.openedWithoutUsingList, owus.rest
WHILE owus #
NIL
DO
ir: MesaAnalyses.InterfaceRecord = owus.first;
IF interrupt#NIL AND interrupt THEN Failure[from, Rope.Concat["interrupted search for ", target]];
IF searchedOpens = NIL THEN searchedOpens ¬ SymTab.Create[case: TRUE];
IF searchedOpens.Insert[ir.type.typeClass, $T]
THEN {
found ¬ TRUE;
cfr ¬ FindCedarDef[target, CedarModuleBody[from, ir.type, verbose, stack, interrupt].cfr.fp.where, deep, verbose, FALSE, searchedOpens, stack, interrupt !Failure => {found ¬ FALSE; CONTINUE}];
IF found THEN GOTO Dun;
};
ENDLOOP;
};
IF (
NOT found)
AND guessDIRECTORY
AND leftmost
THEN {
IF verbose THEN Msg[begin, "Guessing ", target, " missing from DIRECTORY in ", from.fileName];
{ans: FileDWIM.Answer = FileDWIM.ResolveHint[target.Concat[".mesa"], from.fileName, FALSE];
IF ans.fullFileName #
NIL
THEN {
next: Place = [ans.fullFileName, [GetDoc[from, ans.fullFileName], 0], fromStart];
found ¬ TRUE;
cfr ¬ FindCedarDef[target, next, deep, verbose, FALSE, searchedOpens, stack, interrupt !Failure => {found ¬ FALSE; CONTINUE}];
IF found THEN GOTO Dun;
};
IF verbose THEN Msg[end, " ."];
}};
IF NOT found THEN ERROR Failure[from, Rope.Concat["couldn't find ", target]];
cfr ¬ [fp: [[from.fileName, foundLoc, fwdThenBkwd], nameEnd],
recTypeLexer: NIL,
l0: TextNode.LocRelative[[textRoot, 0], after, 1, excludeComments],
i0: after];
IF NOT deep THEN GOTO Dun;
{in:
IO.
STREAM = TiogaStreams.CreateInput[
from: textRoot,
commentHandling: IF excludeComments THEN [FALSE[]] ELSE [TRUE[NIL, NIL]]
];
in.SetIndex[after];
{ENABLE IO.Error => IF stream=in THEN Failure[cfr.fp.where, "IO.Error"];
p: Lexer = MakeLexer[in, NIL, excludeComments];
cfr ¬ FinishDeep[p, cfr];
}}};
ENDCASE => ERROR;
EXITS Dun => cfr ¬ cfr;
};
IF NOT stack.Delete[frame] THEN ERROR;
};
CedarOKBefore:
PROC [searchRope:
ROPE, before:
INT]
RETURNS [ok:
BOOL] = {
Accepts, in reverse: ((nonAlpha ("BEGIN"|"OPEN") white+) | "{" | "[" | ";" | ",") white*
state: {start, gotWhite, gotN, wantG, wantE, wantB, wantP, wantO, dontWantLetter} ¬ start;
WHILE before > 0
DO
char: CHAR = searchRope.Fetch[before ¬ before - 1];
SELECT state
FROM
start =>
SELECT char
FROM
' , '\n, '\t => state ¬ gotWhite;
'[, '{, ';, ', => RETURN [TRUE];
ENDCASE => RETURN [FALSE];
gotWhite =>
SELECT char
FROM
' , '\n, '\t => NULL;
'[, '{, ';, ', => RETURN [TRUE];
'N => state ¬ gotN;
ENDCASE => RETURN [FALSE];
gotN =>
SELECT char
FROM
'I => state ¬ wantG;
'E => state ¬ wantP;
ENDCASE => RETURN [FALSE];
wantG =>
SELECT char
FROM
'G => state ¬ wantE;
ENDCASE => RETURN [FALSE];
wantE =>
SELECT char
FROM
'E => state ¬ wantB;
ENDCASE => RETURN [FALSE];
wantB =>
SELECT char
FROM
'B => state ¬ dontWantLetter;
ENDCASE => RETURN [FALSE];
wantP =>
SELECT char
FROM
'P => state ¬ wantO;
ENDCASE => RETURN [FALSE];
wantO =>
SELECT char
FROM
'O => state ¬ dontWantLetter;
ENDCASE => RETURN [FALSE];
dontWantLetter =>
SELECT char
FROM
IN ['a .. 'z], IN ['A .. 'Z], IN ['0 .. '9] => RETURN [FALSE];
ENDCASE => RETURN [TRUE];
ENDCASE => ERROR;
ENDLOOP;
RETURN [TRUE]};
SkipMumble:
PROC [p: Lexer] = {
DO
SELECT
TRUE
FROM
p.NextIs["PRIVATE"] => LOOP;
p.NextIs["PUBLIC"] => LOOP;
p.NextIs["REF"] => LOOP;
p.NextIs["LONG"] => LOOP;
p.NextIsSeq[LIST["POINTER", "TO"]] => LOOP;
p.NextIsSeq[LIST["MACHINE", "DEPENDENT"]] => LOOP;
p.NextIs["READONLY"] => LOOP;
p.NextIs["BASE"] => LOOP;
p.NextIs["ORDERED"] => LOOP;
ENDCASE => NULL;
EXIT;
ENDLOOP;
};
GetTree:
PROC [p: Lexer, start: Token]
RETURNS [tree: CedarTree] = {
want: {dot, name} ¬ dot;
tree ¬ start.rope;
DO
toke: Token = GetToken[p];
SELECT want
FROM
dot => {
IF toke.rope.Equal["^"] OR toke.rope.Equal[""] THEN LOOP
ELSE IF toke.rope.Equal["."] THEN want ¬ name
ELSE {ReturnToken[p, toke]; EXIT};
};
name => {
IF toke.kind = tokenID
AND
NOT ReservedWord[toke.rope]
THEN {
tree ¬ NEW [FieldSelectionPrivate ¬ [tree, toke.rope]];
want ¬ dot;
}
ELSE {ReturnToken[p, toke]; EXIT};
};
ENDCASE => ERROR;
ENDLOOP;
};
EnterCedarModule:
PROC [from: Place, moduleName:
ROPE, verbose:
BOOL]
RETURNS [Place] = {
ans: FileDWIM.Answer = FileDWIM.ResolveHint[moduleName.Concat[".mesa"], from.fileName, FALSE];
IF ans.fullFileName = NIL THEN ERROR Failure[from, Rope.Cat["couldn't find file ", moduleName, ".mesa"]];
RETURN [[ans.fullFileName, [GetDoc[from, ans.fullFileName], 0], fromStart]]};
CedarModuleBody:
PROC [from: Place, it: MesaAnalyses.InterfaceType, verbose:
BOOL, stack: RefTab.Ref
--Frame ¬
$T--, interrupt:
REF
BOOL]
RETURNS [cfr: CedarFindResult] = {
ans: FileDWIM.Answer = FileDWIM.ResolveHint[it.fileHint, from.fileName, FALSE];
IF ans.fullFileName = NIL THEN ERROR Failure[from, Rope.Cat["couldn't find file ", it.fileHint, " for module ", it.name]];
{next: Place = [ans.fullFileName, [GetDoc[from, ans.fullFileName], 0], fromStart];
cfr ¬ FindCedarDef[it.typeClass, next, FALSE, verbose, FALSE, NIL, stack, interrupt];
}};
MF:
PROC [rope:
ROPE]
RETURNS [fr:
ROPE] =
INLINE {
fr ¬ IF teledebug THEN rope.Flatten[] ELSE rope};
teledebug: BOOL ¬ FALSE;
MakeStack:
PROC
RETURNS [stack: RefTab.Ref
--Frame ¬
$T--] = {
stack ¬ RefTab.Create[hash: HashFrame, equal: CompareFrames];
};
HashFrame:
PROC [key:
REF
ANY]
RETURNS [hash:
CARDINAL]
--RefTab.HashProc-- = {
f: Frame = NARROW[key];
hash ¬ HashTree[f.tree] + RopeHash.FromRope[f.from.fileName] + HashRef[f.from.loc.node] + HashInt[f.from.loc.where];
};
HashTree:
PROC [t: CedarTree]
RETURNS [hash:
CARDINAL] = {
WITH t
SELECT
FROM
x: ROPE => hash ¬ RopeHash.FromRope[x];
x: FieldSelection => hash ¬ HashTree[x.record]*3 + RopeHash.FromRope[x.field];
ENDCASE => ERROR;
};
HashRef:
PROC [ref:
REF
ANY]
RETURNS [hash:
CARDINAL]
= TRUSTED INLINE {hash ¬ HashInt[LOOPHOLE[ref]]};
HashInt:
PROC [i:
INT]
RETURNS [hash:
CARDINAL]
= INLINE {ln: Basics.LongNumber = [li[i]]; hash ¬ ln.lo + ln.hi};
CompareFrames:
PROC [key1, key2:
REF
ANY]
RETURNS [equal:
BOOL] = {
f1: Frame = NARROW[key1];
f2: Frame = NARROW[key2];
equal ¬ CompareTrees[f1.tree, f2.tree] AND f1.from.fileName.Equal[f2.from.fileName, FALSE] AND f1.from.loc = f2.from.loc;
};
CompareTrees:
PROC [t1, t2: CedarTree]
RETURNS [
BOOL] = {
RETURN [
WITH t1
SELECT
FROM
x:
ROPE =>
WITH t2
SELECT
FROM
y: ROPE => x.Equal[y],
y: FieldSelection => FALSE,
ENDCASE => ERROR,
x: FieldSelection =>
WITH t2
SELECT
FROM
y: ROPE => FALSE,
y: FieldSelection => CompareTrees[x.record, y.record] AND x.field.Equal[y.field],
ENDCASE => ERROR,
ENDCASE => ERROR]};
FmtTree:
PROC [t: CedarTree]
RETURNS [rope:
ROPE] = {
rope ¬
WITH t
SELECT
FROM
x: ROPE => x,
x: FieldSelection => FmtTree[x.record].Cat[".", x.field],
ENDCASE => ERROR;
};
GetDoc:
PROC [from: Place, fileName:
ROPE]
RETURNS [doc: TextNode.Ref] ~ {
IF fileName=NIL THEN Failure[from, "Trying to fetch NIL-named document"];
IF fileName.Length[]=0 THEN Failure[from, "Trying to fetch empty-named document"];
doc ¬ DocCache.GetDoc[fileName];
IF doc=NIL THEN Failure[from, IO.PutFR1["Bug: DocCache.GetCache[%g] returned NIL", [rope[fileName]]]];
};
User Interface
cmdEnabled: BOOL ¬ TRUE;
workVerbose: BOOL ¬ TRUE;
DefButt:
PROC [parent:
REF
ANY, clientData:
REF
ANY ¬
NIL,
mouseButton: Menus.MouseButton ¬ red, shift, control:
BOOL ¬
FALSE]
--Menus.ClickProc-- = {
TEditInput.InterpretAtom[NARROW[parent], defAtoms[mouseButton][control][shift]];
};
defAtoms:
ARRAY Menus.MouseButton
OF
ARRAY
--control--
BOOL
OF
ARRAY
--shift--
BOOL
OF
ATOM = [
[[$FindNextDef, $FindNextDefCaseless], [$FindNextDefCtl, $FindNextDefCaselessCtl]],
[[$FindAnyDef, $FindAnyDefCaseless], [$FindAnyDefCtl, $FindAnyDefCaselessCtl]],
[[$FindPrevDef, $FindPrevDefCaseless], [$FindPrevDefCtl, $FindPrevDefCaselessCtl]]];
GetImplButt:
PROC [parent:
REF
ANY, clientData:
REF
ANY ¬
NIL,
mouseButton: Menus.MouseButton ¬ red, shift, control:
BOOL ¬
FALSE]
--Menus.ClickProc-- = {
TEditInput.InterpretAtom[NARROW[parent], implAtoms[mouseButton][control][shift]];
};
implAtoms:
ARRAY Menus.MouseButton
OF
ARRAY
--control--
BOOL
OF
ARRAY
--shift--
BOOL
OF
ATOM = [
[[$LoadImpl, $LoadSmartImpl], [$LoadImpl, $LoadSmartImpl]],
[[$OpenImpl, $OpenSmartImpl], [$OpenImpl, $OpenSmartImpl]],
[[$CloseAndOpenImpl, $CloseAndOpenSmartImpl], [$CloseAndOpenImpl, $CloseAndOpenSmartImpl]]];
DefCommand: PROC [viewer: Viewer ¬ NIL] RETURNS [recordAtom: BOOL ¬ TRUE, quit: BOOL ¬ FALSE] --TiogaOps.CommandProc-- = {[recordAtom, quit] ¬ DefCommandWork[viewer, FALSE, FALSE, open]};
DefDeepCommand: PROC [viewer: Viewer ¬ NIL] RETURNS [recordAtom: BOOL ¬ TRUE, quit: BOOL ¬ FALSE] --TiogaOps.CommandProc-- = {[recordAtom, quit] ¬ DefCommandWork[viewer, TRUE, FALSE, open]};
LoadSmartImplCommand: PROC [viewer: Viewer ¬ NIL] RETURNS [recordAtom: BOOL ¬ TRUE, quit: BOOL ¬ FALSE] --TiogaOps.CommandProc-- = {[recordAtom, quit] ¬ DefCommandWork[viewer, TRUE, TRUE, load]};
OpenSmartImplCommand: PROC [viewer: Viewer ¬ NIL] RETURNS [recordAtom: BOOL ¬ TRUE, quit: BOOL ¬ FALSE] --TiogaOps.CommandProc-- = {[recordAtom, quit] ¬ DefCommandWork[viewer, TRUE, TRUE, open]};
CloseAndOpenSmartImplCommand: PROC [viewer: Viewer ¬ NIL] RETURNS [recordAtom: BOOL ¬ TRUE, quit: BOOL ¬ FALSE] --TiogaOps.CommandProc-- = {[recordAtom, quit] ¬ DefCommandWork[viewer, TRUE, TRUE, closeAndOpen]};
How: TYPE ~ {load, open, closeAndOpen};
DefCommandWork:
PROC [viewer: Viewer, deep, getImpl:
BOOL, how: How]
RETURNS [recordAtom:
BOOL ¬
TRUE, quit:
BOOL ¬
FALSE]
--TiogaOps.CommandProc-- = {
subjectRope: ROPE = TEditOps.GetSelContents[];
msg: ROPE ¬ NIL;
IF NOT cmdEnabled THEN RETURN [FALSE, FALSE];
recordAtom ¬ FALSE;
quit ¬ TRUE;
IF subjectRope.IsEmpty[]
THEN {
Msg[oneLiner, IF getImpl THEN "Select target for impl-search." ELSE "Select target for definition-search."];
SimpleFeedback.Blink[$DefDWIM, $Error];
RETURN;
};
{
start: Place;
def: FoundPlace;
inSelf: BOOL ¬ FALSE;
sought, backErr: ROPE ¬ NIL;
Protected:
PROC ~ {
ENABLE Failure => {
Msg[oneLiner, IF where # noplace THEN Rope.Cat[why, " in ", where.fileName] ELSE why];
SimpleFeedback.Blink[$DefDWIM, $Error];
CONTINUE
};
TEditSelection.CallWithSelAndDocAndTddLocks[viewer, primary, WithLocks];
IF getImpl
THEN {
v: Viewer ~ IF inSelf THEN viewer ELSE TEditDocumentPrivate.DoOpenFile[fileName: def.where.fileName, column: right];
subject, fullFName: ROPE;
cp: FS.ComponentPositions;
[fullFName, cp] ¬ FS.ExpandName[def.where.fileName];
subject ¬ fullFName.Substr[start: cp.base.start, len: cp.base.length].Cat[".", sought];
SELECT how
FROM
load => [] ¬ TEditDocumentPrivate.DoLoadImplFile[v, subject, FALSE];
open => [] ¬ TEditDocumentPrivate.DoOpenImplFile[subject, left, v];
closeAndOpen => [] ¬ TEditDocumentPrivate.DoCloseAndOpenImplFile[v, subject];
ENDCASE => ERROR;
};
SimpleFeedback.ClearHerald[$DefDWIM, $Error];
RETURN};
WithLocks:
PROC [tdd: TEditDocument.TEditDocumentData, tSel: TEditDocument.Selection] = {
root: TextNode.Ref = IF TextNode.Parent[tdd.text]=NIL THEN tdd.text ELSE TextNode.Root[tdd.text];
startLoc: Location = IF root = TextNode.Root[tSel.start.pos.node] THEN tSel.start.pos ELSE [root, 0];
IF tiogaInterrupt # NIL THEN tiogaInterrupt ¬ FALSE;
start ¬ [viewer.file, startLoc, bkwdThenFwd];
def ¬ FindDef[subjectRope, start, deep, workVerbose, tiogaInterrupt];
inSelf ¬ TextNode.Root[startLoc.node] = TextNode.Root[def.where.loc.node];
IF getImpl
THEN {
IF def.where.loc.node # def.nameEnd.node THEN Failure[def.where, "name stretches across node boundary"];
sought ¬ def.where.loc.node.rope.Substr[start: def.where.loc.where, len: def.nameEnd.where-def.where.loc.where+1];
RETURN;
};
IF inSelf
THEN {
tSel.start.pos ¬ def.where.loc;
tSel.end.pos ¬ def.nameEnd;
tSel.granularity ¬ word;
tSel.viewer ¬ viewer;
tSel.data ¬ tdd;
tSel.pendingDelete ¬ FALSE;
TEditOps.RememberCurrentPosition[viewer];
TEditSelection.SetSelLooks[tSel];
TEditSelection.MakeSelection[new: tSel, selection: primary];
TEditInput.CloseEvent[];
TEditRefresh.ScrollToEndOfSel[viewer, FALSE, primary];
}
ELSE {
TEditInput.CloseEvent[];
ViewerForkers.ForkCall[viewer, OpenAndSelect, LIST[NEW [Place ¬ start], NEW [FoundPlace ¬ def]]];
};
};
IF protect THEN backErr ¬ BackStop.Call[Protected] ELSE Protected[];
IF backErr # NIL THEN {SimpleFeedback.Append[$DefDWIM, oneLiner, $Error, backErr]; RETURN [TRUE, FALSE]};
}};
tiogaInterrupt: REF BOOL ¬ NIL;
protect: BOOL ¬ TRUE;
OpenAndSelect:
PROC [data:
REF
ANY]
--ViewerForkers.CallBack-- = {
args: LORA = NARROW[data];
start: REF Place = NARROW[args.first];
end: REF FoundPlace = NARROW[args.rest.first];
first: INT = TextNode.LocNumber[end.where.loc];
last: INT = first + TextNode.LocOffset[end.where.loc, end.nameEnd];
v: Viewer = TEditDocumentPrivate.DoOpenFile[fileName: end.where.fileName, column: right];
IF v =
NIL
THEN {
Msg[oneLiner, "Couldn't open ", end.where.fileName, "."];
SimpleFeedback.Blink[$DefDWIM, $Error];
RETURN;
};
ShowGivenPosition[v, first, last, FALSE];
};
ShowGivenPosition:
PUBLIC
PROC [viewer: Viewer, first, last:
INT, skipCommentNodes:
BOOL ¬
TRUE] = {
Copied from TEditSelectionOpsImpl
DoPosition:
PROC [tdd: TEditDocument.TEditDocumentData, tSel: TEditDocument.Selection] = {
tSel.start.pos ¬ TextNode.LocWithin[tdd.text, first, 1, skipCommentNodes];
tSel.end.pos ¬ TextNode.LocRelative[tSel.start.pos, last-first, 1, skipCommentNodes];
tSel.granularity ¬ word;
tSel.viewer ¬ viewer;
tSel.data ¬ tdd;
tSel.pendingDelete ¬ FALSE;
tSel.insertion ¬ IF TEditProfile.selectionCaret=before THEN before ELSE after;
TEditOps.RememberCurrentPosition[viewer];
TEditSelection.SetSelLooks[tSel];
TEditSelection.MakeSelection[new: tSel, selection: feedback];
TEditRefresh.ScrollToEndOfSel[viewer, FALSE, feedback];
sets bit to cause scroll after refresh
};
TEditSelection.CallWithSelAndDocAndTddLocks[viewer, feedback, DoPosition];
};
AmbushMenuEntry:
PROC [first: Menus.MenuEntry, name:
ROPE, proc: Menus.ClickProc] = {
me: Menus.MenuEntry;
FOR me ¬ first, me.link WHILE NOT me.name.Equal[name] DO NULL ENDLOOP;
me.proc ¬ proc;
};
Setup
NoteProfile:
PROC [changeReason: UserProfile.ProfileChangeReason]
--UserProfile.ProfileChangedProc-- = {
cmdEnabled ¬ UserProfile.Boolean["DefDWIM.Enabled", TRUE];
guessDIRECTORY ¬ UserProfile.Boolean["DefDWIM.GuessDIRECTORY", TRUE];
workVerbose ¬ UserProfile.Boolean["DefDWIM.Verbose", FALSE];
protect ¬ UserProfile.Boolean["DefDWIM.Protect", TRUE];
};
GetTiogaInterrupt:
PROC
RETURNS [interrupt:
REF
BOOL]
= {interrupt ¬ TEditInput.interrupt};
Start:
PROC = {
tiogaInterrupt ¬ GetTiogaInterrupt[!VM.AddressFault => CONTINUE];
TiogaOps.RegisterCommand[$FindAnyDef, DefCommand];
TiogaOps.RegisterCommand[$FindAnyDefCtl, DefDeepCommand];
TiogaOps.RegisterCommand[$LoadSmartImpl, LoadSmartImplCommand];
TiogaOps.RegisterCommand[$OpenSmartImpl, OpenSmartImplCommand];
TiogaOps.RegisterCommand[$CloseAndOpenSmartImpl, CloseAndOpenSmartImplCommand];
AmbushMenuEntry[TEditDocumentPrivate.findMenu, "Def", DefButt];
AmbushMenuEntry[TiogaMenuOps.tiogaMenu.lines[0], "GetImpl", GetImplButt];
UserProfile.CallWhenProfileChanges[NoteProfile];
Register[RecognizeLisp, LispFinder];
Register[RecognizeJS, JSFinder];
Register[RecognizeM3, M3Finder];
Register[RecognizeCedar, CedarFinder];
};
Start[];
END.