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.