TiogaFindImpl.mesa
Copyright Ó 1985, 1986, 1992 by Xerox Corporation. All rights reserved.
Doug Wyatt, March 19, 1992 4:45 pm PST
DIRECTORY
Char,
CharOps,
NodeReader,
Rope,
Rosary,
TextEdit,
TextFind,
TextLooks,
TextNode,
Tioga,
TiogaFind;
TiogaFindImpl: CEDAR PROGRAM
IMPORTS Char, CharOps, NodeReader, Rope, Rosary, TextEdit, TextFind, TextLooks, TextNode
EXPORTS TiogaFind
~ BEGIN
ROPE: TYPE ~ Rope.ROPE;
ROSARY: TYPE ~ Rosary.ROSARY;
Node: TYPE ~ Tioga.Node;
Location: TYPE ~ Tioga.Location;
Looks: TYPE ~ Tioga.Looks;
Event: TYPE ~ Tioga.Event;
Direction: TYPE ~ TextFind.Direction; -- {forward, backward}
refFalse: REF BOOL ~ NEW[BOOL ¬ FALSE];
LiteralSearch: PUBLIC PROC [direction: Direction, loc1, loc2: Location,
target: Node ¬ NIL, targetStart: INT ¬ 0, targetLen: INT ¬ INT.LAST,
case: BOOL ¬ TRUE, match: Match ¬ any,
interrupt: REF BOOL ¬ NIL] RETURNS [node: Node ¬ NIL,
matchStart, matchEnd: INT ¬ 0] ~ {
targetReader: NodeReader.Ref ~ NodeReader.New[target];
targetSize: INT ~ NodeReader.Size[targetReader];
scratchReader: NodeReader.Ref ~ NodeReader.New[];
map: TextFind.CharMap ~ TextFind.CharMapFromCase[case];
found: BOOL ¬ FALSE;
IF interrupt=NIL THEN interrupt ¬ refFalse;
targetStart ¬ MIN[MAX[0, targetStart], targetSize];
targetLen ¬ MIN[MAX[0, targetLen], targetSize-targetStart];
FOR node ¬
(SELECT direction FROM
forward => loc1.node,
backward => loc2.node,
ENDCASE => ERROR),
(SELECT direction FROM
forward => IF node=loc2.node THEN NIL ELSE TextNode.StepForward[node],
backward => IF node=loc1.node THEN NIL ELSE TextNode.StepBackward[node],
ENDCASE => ERROR)
UNTIL node=NIL OR interrupt­ DO
objectReader: NodeReader.Ref ~ NodeReader.New[node, scratchReader];
objectSize: INT ~ NodeReader.Size[objectReader];
i0: INT ~ IF node=loc1.node THEN loc1.where ELSE 0;
i1: INT ~ IF node=loc2.node THEN loc2.where ELSE objectSize;
targetHash: PROC [i: INT] RETURNS [BYTE] ~ {
RETURN[ORD[map[VAL[NodeReader.FetchCharCode[targetReader, i]]]]];
};
objectHash: PROC [i: INT] RETURNS [BYTE] ~ {
RETURN[ORD[map[VAL[NodeReader.FetchCharCode[objectReader, i]]]]];
};
matchProc: PROC [objectStart, targetStart, len: INT] RETURNS [BOOL] ~ {
IF NodeReader.Run[objectReader, objectStart, targetReader, targetStart,
case, len]<len THEN RETURN[FALSE];
matchEnd ¬ (matchStart ¬ objectStart)+len;
IF match=all AND (matchStart#0 OR matchEnd#objectSize) THEN RETURN[FALSE];
IF (match=word OR match=def) AND matchStart>0 AND CharOps.XAlphaNumeric[NodeReader.FetchChar[objectReader, matchStart-1]]
THEN
RETURN[FALSE];
IF match=word AND matchEnd<objectSize AND CharOps.XAlphaNumeric[NodeReader.FetchChar[objectReader, matchEnd]]
THEN
RETURN[FALSE];
IF match=def AND NOT (matchEnd<objectSize AND
NodeReader.FetchChar[objectReader, matchEnd]=xColon)
THEN RETURN[FALSE];
RETURN[TRUE];
};
[found, matchStart, matchEnd] ¬ TextFind.LiteralSearch[direction: direction,
targetHash: targetHash, targetStart: targetStart, targetLen: targetLen,
objectHash: objectHash, objectStart: i0, objectLen: i1-i0,
match: matchProc, interrupt: interrupt];
IF found THEN EXIT;
ENDLOOP;
NodeReader.Free[targetReader];
NodeReader.Free[scratchReader];
IF NOT found THEN RETURN[NIL];
};
NodeAction: TYPE ~ PROC [node: Node, i0, i1: INT] RETURNS [quit: BOOL ¬ FALSE];
MapNodes: PUBLIC PROC [direction: Direction, loc1, loc2: Location, action: MapAction,
interrupt: REF BOOL ¬ NIL] RETURNS [BOOL ¬ FALSE] ~ {
SELECT direction FROM
forward => {
FOR node: Node ← loc1.node, TextNode.StepForward[node] UNTIL node=NIL DO
i0: INT ~ IF node=loc1.node THEN loc1.where ELSE 0;
i1: INT ~ IF node=loc2.node THEN loc2.where ELSE TextEdit.Size[node];
IF action[node, i0, i1] THEN RETURN[TRUE];
IF node=loc2.node OR interrupt­ THEN EXIT;
ENDLOOP;
};
backward => {
FOR node: Node ← loc2.node, TextNode.StepBackward[node] UNTIL node=NIL DO
i0: INT ~ IF node=loc1.node THEN loc1.where ELSE 0;
i1: INT ~ IF node=loc2.node THEN loc2.where ELSE TextEdit.Size[node];
IF action[node, i0, i1] THEN RETURN[TRUE];
IF node=loc1.node OR interrupt­ THEN EXIT;
ENDLOOP;
};
ENDCASE => ERROR;
};
LiteralSearch: PUBLIC PROC [direction: Direction, loc1, loc2: Location,
target: Target ¬ NIL,
case, word, def, looks: BOOL ¬ FALSE, interrupt: REF BOOL ¬ NIL]
RETURNS [node: Node ¬ NIL, matchStart, matchEnd: INT ¬ 0] ~ {
scratchReader: NodeReader.Ref ~ NodeReader.New[];
map: TextFind.CharMap ~ TextFind.CharMapFromCase[case];
found: BOOL ¬ FALSE;
IF interrupt=NIL THEN interrupt ¬ refFalse;
reader: NodeReader.Ref ~ NodeReader.New[node, scratchReader];
size: INT ~ NodeReader.Size[reader];
i0: INT ~ IF node=loc1.node THEN loc1.where ELSE 0;
i1: INT ~ IF node=loc2.node THEN loc2.where ELSE size;
hash1: PROC [i: INT] RETURNS [BYTE] ~ { RETURN[ORD[map[Rope.Fetch[rope1, i]]]] };
hash2: PROC [i: INT] RETURNS [BYTE] ~ { RETURN[ORD[map[Rope.Fetch[rope2, i]]]] };
equal: PROC [index1, index2, len: INT] RETURNS [BOOL] ~ {
IF Rope.Run[s1: rope1, pos1: index1, s2: rope2, pos2: index2,
case: case, len: len]#len THEN RETURN[FALSE];
IF sets1#NIL OR sets2#NIL THEN {
IF NOT RosaryEqual[sets1, sets2, index1, index2, len] THEN RETURN[FALSE];
};
IF looks AND (runs1#NIL OR runs2#NIL) THEN {
IF NOT RosaryEqual[runs1, runs2, index1, index2, len] THEN RETURN[FALSE];
};
IF word THEN {
alpha2: PROC [i: INT] RETURNS [BOOL] ~ {
RETURN[(sets2=NIL OR Rosary.Fetch[sets2, i]=NIL) AND CharOps.AlphaNumeric[Rope.Fetch[rope2, i]]];
};
IF index2>start2 AND alpha2[index2-1] THEN RETURN[FALSE];
IF (index2+len)<(start2+len2) AND alpha2[index2+len] THEN RETURN[FALSE];
};
RETURN[TRUE];
};
[found: found, matchStart: matchStart, matchEnd: matchEnd,
selStart: selStart, selEnd: selEnd, subs: subs] ¬ TextFind.Search[
direction: direction, target: target, size: size, start: i0, len: i1-i0,
matchString: matchString, matchType: matchType, matchProps: matchProps,
substr: substr, word: word, interrupt: interrupt];
IF found THEN EXIT;
IF interrupt­ THEN EXIT;
ENDLOOP;
NodeReader.Free[scratchReader];
IF NOT found THEN RETURN[NIL];
};
Target: TYPE ~ TextFind.Target;
Subs: TYPE ~ TextFind.Subs;
NodeSubstr: PROC [node: Node, start, len: INT] RETURNS [result: Node] ~ {
result ¬ TextNode.NewTextNode[];
[] ¬ TextEdit.ReplaceText[destRoot: NIL, sourceRoot: NIL,
dest: result, source: node, sourceStart: start, sourceLen: len];
};
RosaryRun: PROC [r1: ROSARY, pos1: INT, r2: ROSARY, pos2: INT, len: INT,
match: PROC [item1, item2: REF] RETURNS [BOOL] ¬ NIL] RETURNS [count: INT ¬ 0] ~ {
run1: Rosary.Run ¬ [NIL, IF r1=NIL THEN len ELSE 0];
run2: Rosary.Run ¬ [NIL, IF r2=NIL THEN len ELSE 0];
run: INT ¬ 0;
WHILE count<len DO
IF run1.repeat=0 THEN run1 ¬ Rosary.FetchRun[r1, pos1+count];
IF run2.repeat=0 THEN run2 ¬ Rosary.FetchRun[r2, pos2+count];
IF NOT(run1.item=run2.item OR (match#NIL AND match[run1.item, run2.item])) THEN EXIT;
run ¬ MIN[run1.repeat, run2.repeat, len-count];
count ¬ count+run;
run1.repeat ¬ run1.repeat-run;
run2.repeat ¬ run2.repeat-run;
ENDLOOP;
};
TargetFromNode: PUBLIC PROC [node: Node, start: INT ¬ 0, len: INT ¬ LAST[INT],
pattern: BOOL ¬ FALSE] RETURNS [target: Target] ~ {
reader: NodeReader.Ref ~ NodeReader.New[node];
fetch: TextFind.FetchProc ~ { RETURN[NodeReader.FetchChar[reader, index]] };
substr: TextFind.SubstrProc ~ { RETURN[NodeSubstr[node, start, len]] };
target ¬ TextFind.CreateTarget[size: NodeReader.Size[reader], start: start, len: len,
fetch: fetch, substr: substr, pattern: pattern];
NodeReader.Free[reader];
};
LooksSubset: PROC [item1, item2: REF] RETURNS [BOOL] ~ {
looks1: Looks ~ TextEdit.LooksFromItem[item1];
looks2: Looks ~ TextEdit.LooksFromItem[item2];
RETURN[TextLooks.LooksAND[looks1, looks2]=looks2];
};
Match: TYPE ~ TiogaFind.Match;
xColon: Char.XCHAR ~ Char.Widen[':];
Search: PUBLIC PROC [direction: Direction, loc1, loc2: Location,
target: Target ¬ NIL, case: BOOL ¬ TRUE, match: Match ¬ any,
checkLooks: BOOL ¬ FALSE, looksExact: BOOL ¬ FALSE,
checkComment: BOOL ¬ FALSE, comment: BOOL ¬ FALSE,
checkFormat: BOOL ¬ FALSE, format: ATOM ¬ NIL,
checkStyle: BOOL ¬ FALSE, style: ATOM ¬ NIL,
styleProc: PROC [Node] RETURNS [ATOM] ¬ NIL,
interrupt: REF BOOL ¬ NIL] RETURNS [node: Node ¬ NIL,
matchStart, matchEnd: INT ¬ 0, subs: Subs ¬ NIL] ~ {
scratchReader: NodeReader.Ref ~ NodeReader.New[];
found: BOOL ¬ FALSE;
IF interrupt=NIL THEN interrupt ¬ refFalse;
FOR node ¬
(SELECT direction FROM
forward => loc1.node,
backward => loc2.node,
ENDCASE => ERROR),
(SELECT direction FROM
forward => IF node=loc2.node THEN NIL ELSE TextNode.StepForward[node],
backward => IF node=loc1.node THEN NIL ELSE TextNode.StepBackward[node],
ENDCASE => ERROR)
UNTIL node=NIL OR interrupt­ DO
reader: NodeReader.Ref ~ NodeReader.New[node, scratchReader];
size: INT ~ NodeReader.Size[reader];
IF (checkComment AND TextEdit.GetComment[node]#comment) THEN LOOP;
IF (checkFormat AND TextEdit.GetFormat[node]#format) THEN LOOP;
IF (checkStyle AND styleProc[node]#style) THEN LOOP;
IF target=NIL THEN { matchStart ¬ 0; matchEnd ¬ size; EXIT }
ELSE {
i0: INT ~ IF node=loc1.node THEN loc1.where ELSE 0;
i1: INT ~ IF node=loc2.node THEN loc2.where ELSE size;
matchString: TextFind.MatchStringProc ~ {
WITH text SELECT FROM
text: Node => {
IF Rope.Run[s1: node.rope, pos1: index, s2: text.rope, pos2: start,
len: len, case: case]<len THEN RETURN[FALSE];
IF (node.charSets#NIL OR text.charSets#NIL) AND
RosaryRun[r1: node.charSets, pos1: index, r2: text.charSets, pos2: start,
len: len]<len THEN RETURN[FALSE];
RETURN[TRUE];
};
rope: ROPE => {
IF Rope.Run[s1: node.rope, pos1: index, s2: rope, pos2: start,
len: len, case: case]<len THEN RETURN[FALSE];
IF node.charSets#NIL AND
RosaryRun[r1: node.charSets, pos1: index, r2: NIL, pos2: start,
len: len]<len THEN RETURN[FALSE];
RETURN[TRUE];
};
ENDCASE => ERROR;
};
matchProps: TextFind.MatchStringProc ~ {
WITH text SELECT FROM
text: Node => {
IF (text.runs#NIL OR (looksExact AND node.runs#NIL)) AND
RosaryRun[r1: node.runs, pos1: index, r2: text.runs, pos2: start, len: len,
match: IF looksExact THEN NIL ELSE LooksSubset]<len THEN RETURN[FALSE];
RETURN[TRUE];
};
rope: ROPE => {
IF (looksExact AND node.runs#NIL) AND
RosaryRun[r1: node.runs, pos1: index, r2: NIL, pos2: start, len: len,
match: NIL]<len THEN RETURN[FALSE];
RETURN[TRUE];
};
ENDCASE => ERROR;
};
matchType: TextFind.MatchTypeProc ~ {
char: Char.XCHAR ~ NodeReader.FetchChar[reader, index];
RETURN[SELECT type FROM
any => TRUE,
alpha => CharOps.XAlphaNumeric[char],
nonalpha => NOT CharOps.XAlphaNumeric[char],
blank => CharOps.XBlank[char],
nonblank => NOT CharOps.XBlank[char],
ENDCASE => ERROR];
};
matchBound: TextFind.MatchBoundProc ~ {
RETURN[SELECT bound FROM
start => SELECT match FROM
any => TRUE,
word, def => NOT(index>0 AND matchType[index-1, alpha]),
all => index=0,
ENDCASE => FALSE,
end => SELECT match FROM
any => TRUE,
word => NOT(index<size AND matchType[index, alpha]),
def => (index<size AND NodeReader.FetchChar[reader, index]=xColon),
all => index=size,
ENDCASE => FALSE,
ENDCASE => FALSE];
};
substr: TextFind.SubstrProc ~ { RETURN[NodeSubstr[node, start, len]] };
[found: found, selStart: matchStart, selEnd: matchEnd, subs: subs] ¬ TextFind.Search[
direction: direction, target: target, size: size, start: i0, len: i1-i0,
substr: substr, matchString: matchString, matchType: matchType,
matchProps: (IF checkLooks THEN matchProps ELSE NIL),
matchBound: matchBound, interrupt: interrupt];
IF found THEN EXIT;
};
ENDLOOP;
NodeReader.Free[scratchReader];
IF NOT found THEN RETURN[NIL];
};
Replace: PUBLIC PROC [dest: Node, destStart: INT ¬ 0, destLen: INT ¬ INT.LAST,
source: Node, sourceStart: INT ¬ 0, sourceLen: INT ¬ INT.LAST,
pattern: BOOL ¬ FALSE, subs: Subs ¬ NIL, event: Event ¬ NIL]
RETURNS [resultStart, resultLen: INT ¬ 0] ~ {
reader: NodeReader.Ref ~ NodeReader.New[source];
fetch: TextFind.FetchProc ~ { RETURN[NodeReader.FetchChar[reader, index]] };
put: PROC [source: Node, start, len: INT] RETURNS [repStart, repLen: INT] ~ {
[repStart, repLen] ¬ TextEdit.ReplaceText[destRoot: NIL, sourceRoot: NIL,
dest: dest, destStart: destStart, destLen: destLen,
source: source, sourceStart: start, sourceLen: len, event: event];
destStart ¬ repStart+repLen; destLen ¬ 0;
resultLen ¬ resultLen+repLen;
};
replace: TextFind.ReplaceProc ~ { [] ¬ put[source, start, len] };
substitute: TextFind.SubstituteProc ~ {
WITH text SELECT FROM
text: Node => {
repStart, repLen: INT;
[repStart, repLen] ¬ put[text, start, len];
IF nameLen>0 THEN {
looks: Looks ~ TextEdit.FetchLooks[source, nameStart];
TextEdit.ChangeLooks[root: NIL, text: dest, remove: Tioga.allLooks,
add: looks, start: repStart, len: repLen, event: event];
};
};
ENDCASE;
};
TextFind.Replace[replace: replace, substitute: substitute,
size: TextEdit.Size[source], start: sourceStart, len: sourceLen, fetch: fetch,
pattern: pattern, subs: subs];
IF destLen>0 THEN [] ¬ put[NIL, 0, 0];
resultStart ¬ destStart-resultLen;
NodeReader.Free[reader];
};
Apply: PUBLIC PROC [proc: TiogaFind.ApplyProc, loc1, loc2: Location,
target: Target ¬ NIL, case: BOOL ¬ TRUE, match: Match ¬ any,
checkLooks: BOOL ¬ FALSE, looksExact: BOOL ¬ FALSE,
checkComment: BOOL ¬ FALSE, comment: BOOL ¬ FALSE,
checkFormat: BOOL ¬ FALSE, format: ATOM ¬ NIL,
checkStyle: BOOL ¬ FALSE, style: ATOM ¬ NIL,
styleProc: PROC [Node] RETURNS [ATOM] ¬ NIL,
interrupt: REF BOOL ¬ NIL] RETURNS [count: INT ¬ 0] ~ {
node: Node ¬ loc1.node;
where: INT ¬ loc1.where;
matchStart, matchEnd, from, delta: INT ¬ 0;
subs: Subs ¬ NIL;
continue, bumpCount: BOOL ¬ FALSE;
UNTIL node=NIL DO
IF node=loc2.node AND where>=loc2.where THEN EXIT;
[node: node, matchStart: matchStart, matchEnd: matchEnd, subs: subs] ¬ Search[
direction: forward, loc1: [node, where], loc2: loc2,
target: target, case: case, match: match,
checkLooks: checkLooks, looksExact: looksExact,
checkComment: checkComment, comment: comment,
checkFormat: checkFormat, format: format,
checkStyle: checkStyle, style: style, styleProc: styleProc,
interrupt: interrupt];
IF node=NIL THEN RETURN;
[continue: continue, bumpCount: bumpCount, from: from, delta: delta] ¬
proc[node: node, matchStart: matchStart, matchEnd: matchEnd, subs: subs];
IF bumpCount THEN count ¬ count+1;
IF NOT continue THEN EXIT;
IF node=loc2.node THEN loc2.where ¬ loc2.where+delta;
IF target#NIL THEN { where ¬ from }
ELSE { node ¬ TextNode.StepForward[node]; where ¬ 0 };
ENDLOOP;
};
END.