TiogaFindImpl.mesa
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
derived from EditorFind.Mesa of Laurel 6
Paxton, February 23, 1983 4:13 pm
Maxwell, January 5, 1983 4:03 pm
Russ Atkinson, July 25, 1983 3:21 pm
Michael Plass, March 21, 1985 2:41:13 pm PST
Doug Wyatt, September 23, 1986 5:21:27 pm PDT
DIRECTORY
Basics,
LooksReader,
Rope,
RopeEdit,
RopeReader,
RunReader,
TextFindPrivate,
TextLooks,
Tioga;
TiogaFindImpl: CEDAR PROGRAM
IMPORTS Basics, LooksReader, Rope, RopeEdit, RopeReader, RunReader, TextFindPrivate, TextLooks, Tioga
EXPORTS Tioga
= BEGIN OPEN Tioga;
ROPE: TYPE ~ Rope.ROPE;
Runs: TYPE ~ TextLooks.Runs;
Looks: TYPE ~ TextLooks.Looks;
noLooks: Looks ~ TextLooks.noLooks;
Finder: TYPE ~ REF FinderRec;
FinderRec: PUBLIC TYPE ~ TextFindPrivate.FinderRecord;
TextFindImpl
MalformedPattern: PUBLIC ERROR [ec: PatternErrorCode] = CODE;
FinderFromParts: PROC [patternRope: ROPE, patternRuns: Runs, literal, word, ignoreLooks, ignoreCase, addBounds: BOOLFALSE, patternStart: INT ← 0, patternLen: INTLAST[INT]] RETURNS [finder: Finder] = {
NewLooks: PROC [num: NAT] RETURNS [array: REF TextFindPrivate.LooksArray] = {
array ← NEW[TextFindPrivate.LooksArray[num]];
FOR i:NAT IN [0..num) DO array[i] ← noLooks; ENDLOOP
};
char, patternChar: CHAR ← 377C;
pLen: INT;
patternLength, plen, psIndex, nameCount: NAT ← 0;
nameList: LIST OF ROPE; -- in reverse order of appearance
nameLooksList: LIST OF Looks;
nameLooks: Looks;
insideNamedPat: BOOLFALSE;
IF addBounds THEN { -- add |'s to both ends of pattern
IF literal THEN { -- put quotes before special chars in the pattern
new: Rope.ROPE;
AddQuotes: PROC [c: CHAR] RETURNS [stop: BOOL] = {
IF ~RopeEdit.BlankChar[c] AND ~RopeEdit.AlphaNumericChar[c] THEN
new ← Rope.Cat[new, "'"]; -- quote chars that are not blank or alpha or digit
new ← Rope.Cat[new, Rope.FromChar[c]];
RETURN [FALSE]
};
[] ← Rope.Map[base: patternRope, action: AddQuotes];
patternRope ← new; literal ← FALSE;
};
patternRope ← Rope.Cat["|", Rope.Cat[patternRope, "|"]];
};
pLen ← Rope.Size[patternRope];
patternStart ← MIN[patternStart, pLen];
IF (patternLen ← MIN[patternLen, pLen-patternStart]) > TextFindPrivate.MaxPatternLength THEN ERROR MalformedPattern[toobig];
patternLength ← plen ← patternLen;
finder ← NEW[FinderRec]; { -- body of Create;
PatternProc: TYPE = PROC [char: CHAR, looks: Looks, ignoreCase: BOOL];
patProc: PatternProc = IF literal THEN LitChar ELSE PatChar;
GetLooks: PROC RETURNS [lks: Looks] = {
RETURN [IF finder.lksReader = NIL THEN noLooks ELSE
LooksReader.Get[finder.lksReader ! RunReader.NoMoreRuns => {
lks ← noLooks; CONTINUE
}]]
};
LitChar: PatternProc = {
IF looks # noLooks THEN {
IF finder.patternLooks = NIL THEN finder.patternLooks ← NewLooks[patternLength];
finder.patternLooks[psIndex] ← looks;
};
IF ignoreCase AND (char IN ['A..'Z] OR char IN ['a..'z]) THEN {
TRUSTED {finder.patternArray[psIndex] ← [pattern[char+200B]]};
200B tells matcher to check both upper and lower
IF psIndex = 0 THEN {
finder.firstPatternCharIsNormal ← TRUE;
finder.firstPatChar1 ← RopeEdit.UpperCase[char];
finder.firstPatChar2 ← RopeEdit.LowerCase[char];
};
IF psIndex = patternLength-1 THEN {
finder.lastPatternCharIsNormal ← TRUE;
finder.lastPatChar1 ← RopeEdit.UpperCase[char];
finder.lastPatChar2 ← RopeEdit.LowerCase[char];
}
}
ELSE {
TRUSTED {finder.patternArray[psIndex] ← [pattern[char]]};
IF psIndex = patternLength-1 THEN {
finder.lastPatternCharIsNormal ← TRUE;
finder.lastPatChar1 ← char;
finder.lastPatChar2 ← 0C;
};
IF psIndex = 0 THEN {
finder.firstPatternCharIsNormal ← TRUE;
finder.firstPatChar1 ← char;
finder.firstPatChar2 ← 0C;
}
}
};
NotChar: PatternProc = {
IF looks # noLooks THEN {
IF finder.patternLooks = NIL THEN finder.patternLooks ← NewLooks[patternLength];
finder.patternLooks[psIndex] ← looks;
};
IF ignoreCase THEN TRUSTED {finder.patternArray[psIndex] ← [not[char+200B]]}
ELSE TRUSTED {finder.patternArray[psIndex] ← [not[char]]};
};
PatChar: PatternProc = {
IF looks # noLooks AND finder.patternLooks = NIL THEN
finder.patternLooks ← NewLooks[patternLength];
IF finder.patternLooks # NIL THEN finder.patternLooks[psIndex] ← looks;
SELECT char FROM
'' => {
IF RopeReader.GetIndex[finder.ropeReader] >= plen THEN ERROR MalformedPattern[endquote];
patternLength ← patternLength-1;
LitChar[RopeReader.Get[finder.ropeReader], GetLooks[], FALSE]
};
IN ['A .. 'Z], IN ['a .. 'z] => LitChar[char, looks, ignoreCase];
'~ => {
IF RopeReader.GetIndex[finder.ropeReader] >= plen THEN ERROR MalformedPattern[endtilda];
patternLength ← patternLength-1;
char ← RopeReader.Get[finder.ropeReader];
looks ← GetLooks[];
SELECT char FROM
'' => {
IF RopeReader.GetIndex[finder.ropeReader] >= plen THEN
ERROR MalformedPattern[endquote];
patternLength ← patternLength-1;
NotChar[RopeReader.Get[finder.ropeReader], GetLooks[], FALSE]
};
IN ['A .. 'Z], IN ['a .. 'z] => NotChar[char, looks, ignoreCase];
'% => TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.oneNonBlankPattern]]};
'$ =>
IF psIndex > 0 AND finder.patternArray[psIndex-1]=[pattern[TextFindPrivate.anyNonBlankPattern]] AND
(finder.patternLooks=NIL OR finder.patternLooks[psIndex-1]=looks) THEN { -- change to max
patternLength ← patternLength-1;
psIndex ← psIndex-1;
TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.maxNonBlankPattern]]};
}
ELSE { -- new entry
TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.anyNonBlankPattern]]};
finder.stackSize ← finder.stackSize+1;
};
'@ => TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.oneNonAlphaPattern]]};
'& =>
IF psIndex > 0 AND finder.patternArray[psIndex-1]=[pattern[TextFindPrivate.anyNonAlphaPattern]] AND
(finder.patternLooks=NIL OR finder.patternLooks[psIndex-1]=looks) THEN { -- change to max
patternLength ← patternLength-1;
psIndex ← psIndex-1;
TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.maxNonAlphaPattern]]}
}
ELSE { -- new entry
TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.anyNonAlphaPattern]]};
finder.stackSize ← finder.stackSize+1;
};
ENDCASE => TRUSTED {finder.patternArray[psIndex] ← [not[char]]};
};
'# => TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.oneCharPattern]]};
'* =>
IF psIndex > 0 AND finder.patternArray[psIndex-1]=[pattern[TextFindPrivate.anyStringPattern]] AND
(finder.patternLooks=NIL OR finder.patternLooks[psIndex-1]=looks) THEN { -- change to max
psIndex ← psIndex-1;
patternLength ← patternLength-1;
finder.stackSize ← MAX[1, finder.stackSize];
TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.maxStringPattern]]};
}
ELSE { -- new entry
TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.anyStringPattern]]};
IF looks # noLooks THEN finder.stackSize ← finder.stackSize+1;
};
'% => TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.oneBlankPattern]]};
'$ =>
IF psIndex > 0 AND finder.patternArray[psIndex-1]=[pattern[TextFindPrivate.anyBlankPattern]] AND
(finder.patternLooks=NIL OR finder.patternLooks[psIndex-1]=looks) THEN { -- change to max
patternLength ← patternLength-1;
psIndex ← psIndex-1;
TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.maxBlankPattern]]};
}
ELSE { -- new entry
TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.anyBlankPattern]]};
finder.stackSize ← finder.stackSize+1;
};
'@ => TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.oneAlphaPattern]]};
'& =>
IF psIndex > 0 AND finder.patternArray[psIndex-1]=[pattern[TextFindPrivate.anyAlphaPattern]] AND
(finder.patternLooks=NIL OR finder.patternLooks[psIndex-1]=looks) THEN { -- change to max
patternLength ← patternLength-1;
psIndex ← psIndex-1;
TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.maxAlphaPattern]]};
}
ELSE { -- new entry
TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.anyAlphaPattern]]};
finder.stackSize ← finder.stackSize+1;
};
'| => {
TRUSTED {finder.patternArray[psIndex] ←
[pattern[IF psIndex = 0 THEN TextFindPrivate.leftBoundaryPattern ELSE TextFindPrivate.rightBoundaryPattern]]
};
IF psIndex # 0 AND psIndex # patternLength-1 THEN
ERROR MalformedPattern[boundary];
IF psIndex = patternLength-1 THEN { -- right boundary
finder.lastPatternCharIsNormal ← TRUE; finder.lastPatChar1 ← TextFindPrivate.rightBoundaryPattern;
};
IF psIndex = 0 THEN { --left boundary
finder.firstPatternCharIsNormal ← TRUE; finder.firstPatChar1 ← TextFindPrivate.leftBoundaryPattern;
}
};
'< => {
nameStart: INT ← RopeReader.GetIndex[finder.ropeReader]; -- index of char after the <
nameLen: INT ← 0;
IF insideNamedPat THEN ERROR MalformedPattern[missingNameEnd];
insideNamedPat ← TRUE;
nameLooks ← looks; -- remember the looks of the <
TRUSTED {finder.patternArray[psIndex] ← [startname[nameCount]]};
DO SELECT RopeReader.Peek[finder.ropeReader !
RopeReader.ReadOffEnd => GOTO BadName] FROM -- scan to end of name
': => { -- pattern follows
[] ← RopeReader.Get[finder.ropeReader];
[] ← GetLooks[];
patternLength ← patternLength-(nameLen+1);
EXIT;
};
'> => { -- no pattern given, so insert a phony *
psIndex ← psIndex + 1;
PatChar['*, looks, ignoreCase]; -- use looks from the '<
patternLength ← patternLength-nameLen+1;
EXIT;
};
ENDCASE => { -- part of the name
nameLen ← nameLen+1;
[] ← RopeReader.Get[finder.ropeReader];
[] ← GetLooks[];
};
ENDLOOP;
nameList ← CONS[Rope.Substr[patternRope, nameStart, nameLen], nameList];
nameLooksList ← CONS[nameLooks, nameLooksList];
EXITS BadName => ERROR MalformedPattern[missingNameEnd]
};
'> => {
IF ~insideNamedPat THEN ERROR MalformedPattern[unmatchedNameEnd];
insideNamedPat ← FALSE;
TRUSTED {finder.patternArray[psIndex] ← [endname[nameCount]]};
nameCount ← nameCount+1;
};
'{ => {
finder.leftBracketSeen ← TRUE;
TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.leftBracketPattern]]};
};
'} => {
IF finder.rightBracketSeen THEN
TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.nopPattern]]} -- use first right Bracket in pattern
ELSE {
finder.rightBracketSeen ← TRUE;
TRUSTED {finder.patternArray[psIndex] ← [pattern[TextFindPrivate.rightBracketPattern]]};
}
};
ENDCASE => {
TRUSTED {finder.patternArray[psIndex] ← [pattern[char]]};
IF psIndex = patternLength-1 THEN {
finder.lastPatternCharIsNormal ← TRUE;
finder.lastPatChar1 ← char;
finder.lastPatChar2 ← 0C;
};
IF psIndex = 0 THEN {
finder.firstPatternCharIsNormal ← TRUE;
finder.firstPatChar1 ← char;
finder.firstPatChar2 ← 0C;
}
}
}; -- end of PatChar
IF word THEN finder.wordSearch ← TRUE
so Try will know to make sure don't have adjacent alphanumerics
ELSE IF patternLength=2 AND ~literal AND ~ignoreLooks AND Rope.Fetch[patternRope, patternStart]='# AND Rope.Fetch[patternRope, patternStart+1]='* THEN { -- for looks-only searches
finder.looks ← TextLooks.FetchLooks[patternRuns, patternStart];
finder.looksOnly ← TRUE;
finder.runReader ← RunReader.Create[];
RETURN;
};
finder.patternArray ← NEW[TextFindPrivate.PatternArray[patternLength]];
finder.ropeReader ← RopeReader.Create[];
RopeReader.SetPosition[finder.ropeReader, patternRope, patternStart];
IF patternRuns # NIL AND ~ignoreLooks THEN {
finder.lksReader ← LooksReader.Create[];
LooksReader.SetPosition[finder.lksReader, patternRuns, patternStart];
};
psIndex ← 0;
DO -- unpack the pattern
char: CHAR ← RopeReader.Get[finder.ropeReader ! RopeReader.ReadOffEnd => EXIT];
patProc[char, GetLooks[], ignoreCase];
psIndex ← psIndex + 1;
ENDLOOP;
IF insideNamedPat THEN ERROR MalformedPattern[missingNameEnd]; -- mfp
finder.length ← patternLength;
IF finder.stackSize > 0 THEN {
finder.stackSize ← finder.stackSize+1;
finder.textPosStack ← NEW[TextFindPrivate.TextStackArray[finder.stackSize]];
finder.textLenStack ← NEW[TextFindPrivate.TextStackArray[finder.stackSize]];
finder.patternPosStack ← NEW[TextFindPrivate.PatternStackArray[finder.stackSize]];
};
IF nameList # NIL THEN {
finder.nameArray ← NEW[TextFindPrivate.NameArray[nameCount]];
FOR i:NAT DECREASING IN [0..nameCount) DO
finder.nameArray[i].name ← nameList.first;
finder.nameArray[i].looks ← nameLooksList.first;
nameList ← nameList.rest;
nameLooksList ← nameLooksList.rest;
ENDLOOP;
};
}; --of body of Create
};
FinderFromText: PUBLIC PROC [pattern: Text,
literal, word, ignoreLooks, ignoreCase, addBounds: BOOLFALSE]
RETURNS
[finder: Finder] = {
RETURN [FinderFromParts[GetRope[pattern.node], GetRuns[pattern.node], literal, word, ignoreLooks, ignoreCase, addBounds, pattern.start, pattern.len]]
};
FinderFromRope: PUBLIC PROC [pattern: ROPE, start: INT ← 0, len: INT ← maxLen,
literal, word, ignoreCase, addBounds: BOOLFALSE]
RETURNS [finder: Finder] = {
RETURN [FinderFromParts[pattern, NIL, literal, word, TRUE, ignoreCase, addBounds, start, len]];
};
NameLoc: PUBLIC PROC [finder: Finder, name: ROPE] RETURNS [at, atEnd: INT ← 0] = {
IF finder # NIL THEN {
nameArray: REF TextFindPrivate.NameArray ~ finder.nameArray;
IF nameArray = NIL THEN RETURN;
FOR i: NAT IN [0..nameArray.length) DO
IF Rope.Equal[nameArray[i].name, name] THEN
RETURN [nameArray[i].at, nameArray[i].atEnd];
ENDLOOP;
};
};
NameLooks: PUBLIC PROC [finder: Finder, name: ROPE] RETURNS [Looks ← noLooks] = {
IF finder # NIL THEN {
nameArray: REF TextFindPrivate.NameArray ~ finder.nameArray;
IF nameArray = NIL THEN RETURN;
FOR i: NAT IN [0..nameArray.length) DO
IF Rope.Equal[nameArray[i].name, name] THEN RETURN [nameArray[i].looks];
ENDLOOP;
};
};
TextFind2Impl
CharBits: PROC [c: CHAR] RETURNS [CHAR] = INLINE {
RETURN [LOOPHOLE[Basics.BITAND[LOOPHOLE[TextFindPrivate.CharMask],LOOPHOLE[c]], CHAR]];
};
TryToFind: PROC [finder: Finder, rope: ROPE, runs: Runs, start: INT ← 0, len: INT ← maxLen, looksExact: BOOLFALSE, interrupt: REF BOOLNIL] RETURNS [found: BOOL, at, atEnd, before, after: INT] = {
patternPosStack: REF TextFindPrivate.PatternStackArray ~ finder.patternPosStack;
textPosStack: REF TextFindPrivate.TextStackArray ~ finder.textPosStack;
textLenStack: REF TextFindPrivate.TextStackArray ~ finder.textLenStack;
patternArray: REF TextFindPrivate.PatternArray ~ finder.patternArray;
length: NAT ~ finder.length;
patternLooks: REF TextFindPrivate.LooksArray ~ finder.patternLooks;
nameArray: REF TextFindPrivate.NameArray ~ finder.nameArray;
firstPatChar1: CHAR ~ finder.firstPatChar1;
firstPatChar2: CHAR ~ finder.firstPatChar2;
ropeReader: RopeReader.Ref ~ finder.ropeReader;
lksReader: LooksReader.Ref ~ finder.lksReader;
looks: Looks ~ finder.looks;
looksOnly: BOOL ~ finder.looksOnly;
rightBracketSeen: BOOL ← finder.rightBracketSeen;
firstPatternCharIsNormal: BOOL ~ finder.firstPatternCharIsNormal;
stackPtr, patternPos, patternAnchor: NAT ← 0;
char, patternChar: CHAR ← 377C;
charType: RopeEdit.CharProperty;
beginPos, endPos, textPos, textAnchor, end, size: INT;
psLength: NAT;
LooksMatch: PROC [txtpos: INT, ppos: NAT] RETURNS [BOOL] = {
patlks, sourcelks: Looks;
IF (patlks ← patternLooks[ppos]) = noLooks THEN RETURN [TRUE];
IF runs=NIL THEN RETURN [FALSE]; -- pattern has looks and text doesn't
IF txtpos NOT IN [start..end) THEN RETURN [FALSE]; -- boundary char has no looks
LooksReader.SetPosition[lksReader,runs,txtpos];
sourcelks ← LooksReader.Get[lksReader];
RETURN [patlks=(IF looksExact THEN sourcelks ELSE TextLooks.LooksAND[sourcelks,patlks])]
};
GetChar: PROC [txtpos: INT] RETURNS [char: CHAR] = {
SELECT txtpos FROM
IN [start..end) => { -- read the character from the rope
char ← Rope.Fetch[rope, txtpos];
RopeReader.SetPosition[ropeReader,rope,txtpos+1];
};
ENDCASE => ERROR;
};
PropTest: TYPE = { eq, ne, any };
MaxCount: PROC [propTest: PropTest, property: RopeEdit.CharProperty ← illegal]
RETURNS [count: INT] = {
count ← 0;
DO
IF textPos+count NOT IN [start..end) THEN EXIT;
char ← GetChar[textPos+count];
IF propTest=eq THEN IF RopeEdit.GetCharProp[char] # property THEN EXIT ELSE NULL
ELSE IF propTest=ne AND RopeEdit.GetCharProp[char]=property THEN EXIT ELSE NULL;
IF patternLooks # NIL AND patternLooks[patternPos] # noLooks
AND NOT LooksMatch[textPos+count, patternPos] THEN EXIT;
count ← count+1;
ENDLOOP;
};
size ← Rope.Size[rope];
start ← MIN[MAX[0,start],size];
len ← MIN[MAX[0,len],size-start];
end ← start+len;
found ← FALSE;
IF looksOnly THEN {
looksReader: LooksReader.Ref ~ LooksReader.GetLooksReader[];
LooksReader.SetPosition[looksReader, runs, start];
at ← start;
WHILE at < end DO
lks: Looks ← LooksReader.InlineGet[looksReader];
IF NOT looksExact THEN lks ← TextLooks.LooksAND[lks, looks];
IF lks = looks THEN EXIT; -- have found a match
IF interrupt#NIL AND interrupt^ THEN at ← end
ELSE at ← at+1;
ENDLOOP;
LooksReader.FreeLooksReader[looksReader];
IF at >= end THEN RETURN; -- failed to find a match
RETURN [TRUE, at, at+1, at, at+1]
};
psLength ← length;
UNTIL psLength = 0 DO -- discard trailing "any's"
SELECT TextFindPrivate.Pat[patternArray[psLength-1]] FROM
TextFindPrivate.anyStringPat, TextFindPrivate.anyAlphaPat, TextFindPrivate.anyNonAlphaPat, TextFindPrivate.anyBlankPat, TextFindPrivate.anyNonBlankPat => NULL;
ENDCASE => EXIT;
psLength ← psLength-1;
ENDLOOP;
IF psLength=0 THEN RETURN [TRUE,start,start,start,start]; -- null pattern
at ← start;
RopeReader.SetPosition[ropeReader, rope, at];
DO -- text loop
IF firstPatternCharIsNormal THEN {
IF firstPatChar1 = TextFindPrivate.leftBoundaryPattern THEN {
IF at > 0 THEN GOTO Return; -- failure since not at left boundary
patternPos ← 1; textPos ← 0
}
ELSE { -- search for next instance of first pattern char
at ← MAX[start,at];
RopeReader.SetIndex[ropeReader, at];
UNTIL at >= end DO
SELECT RopeReader.Get[ropeReader] FROM
firstPatChar1, firstPatChar2 =>
IF patternLooks=NIL OR LooksMatch[at,0] THEN EXIT;
ENDCASE;
IF interrupt#NIL AND interrupt^ THEN GOTO Return;
at ← at+1;
ENDLOOP;
patternPos ← 1; textPos ← at + 1
}
}
ELSE { patternPos ← 0; textPos ← at };
IF at >= end THEN EXIT;
stackPtr ← patternAnchor ← 0;
before ← beginPos ← textAnchor ← at;
DO -- pattern loop
IF patternPos >= psLength THEN { -- have finished pattern
found ← TRUE;
textPos ← MIN[end,textPos]; -- in case used final boundary char in making the match
at ← MAX[start,beginPos]; -- in case used initial boundary char in making the match
before ← MAX[before,start]; -- in case used initial boundary char in making the match
atEnd ← IF rightBracketSeen THEN endPos ELSE textPos;
after ← textPos;
GO TO Return
};
IF interrupt#NIL AND interrupt^ THEN GO TO Return;
WITH p:patternArray[patternPos] SELECT FROM
startname => { nameArray[p.index].at ← textPos; patternPos ← patternPos+1 };
endname => { nameArray[p.index].atEnd ← textPos; patternPos ← patternPos+1 };
not => { -- check that next character is not the one in this pattern element
IF textPos NOT IN [start..end) THEN EXIT;
char ← GetChar[textPos];
SELECT patternChar ← p.char FROM
char => IF patternLooks=NIL OR LooksMatch[textPos,patternPos] THEN EXIT;
>= TextFindPrivate.EightBit => { -- check both upper and lower case
IF (SELECT patternChar ← CharBits[patternChar] FROM
IN ['A..'Z] => patternChar = RopeEdit.UpperCase[char],
IN ['a ..'z] => patternChar = RopeEdit.LowerCase[char],
ENDCASE => patternChar = char)
AND (patternLooks=NIL OR LooksMatch[textPos,patternPos])
THEN EXIT
}; -- chars match
ENDCASE;
patternPos ← patternPos+1; textPos ← textPos + 1
};
pattern => { SELECT patternChar ← p.char FROM
TextFindPrivate.leftBracketPattern => { beginPos ← textPos; patternPos ← patternPos+1 };
TextFindPrivate.rightBracketPattern => { endPos ← textPos; patternPos ← patternPos+1 };
TextFindPrivate.nopPattern => patternPos ← patternPos+1;
TextFindPrivate.anyStringPattern => {
IF patternLooks # NIL AND patternLooks[patternPos] # noLooks THEN {
stackPtr ← stackPtr + 1;
textPosStack[stackPtr] ← textPos;
textLenStack[stackPtr] ← -1;
patternPosStack[stackPtr] ← patternPos
}
ELSE { textAnchor ← textPos; patternAnchor ← patternPos + 1; stackPtr ← 0 };
patternPos ← patternPos + 1
};
TextFindPrivate.anyNonAlphaPattern, TextFindPrivate.anyAlphaPattern, TextFindPrivate.anyNonBlankPattern, TextFindPrivate.anyBlankPattern => {
stackPtr ← stackPtr + 1;
textPosStack[stackPtr] ← textPos;
textLenStack[stackPtr] ← -1;
patternPosStack[stackPtr] ← patternPos;
patternPos ← patternPos + 1
};
TextFindPrivate.maxStringPattern => {
IF patternLooks # NIL AND patternLooks[patternPos] # noLooks THEN {
stackPtr ← stackPtr + 1;
textPosStack[stackPtr] ← textPos;
textLenStack[stackPtr] ← MaxCount[any];
patternPosStack[stackPtr] ← patternPos
}
ELSE {
stackPtr ← 1;
textPosStack[stackPtr] ← textPos;
textLenStack[stackPtr] ← end-textPos;
patternPosStack[stackPtr] ← patternPos
};
textPos ← textPos + textLenStack[stackPtr];
patternPos ← patternPos + 1
};
TextFindPrivate.maxNonAlphaPattern, TextFindPrivate.maxAlphaPattern, TextFindPrivate.maxNonBlankPattern, TextFindPrivate.maxBlankPattern => {
stackPtr ← stackPtr + 1;
textPosStack[stackPtr] ← textPos;
textLenStack[stackPtr] ← SELECT patternChar FROM
TextFindPrivate.maxNonAlphaPattern => MaxCount[ne, alphaNumeric],
TextFindPrivate.maxAlphaPattern => MaxCount[eq, alphaNumeric],
TextFindPrivate.maxNonBlankPattern => MaxCount[ne, white],
TextFindPrivate.maxBlankPattern => MaxCount[eq, white],
ENDCASE => ERROR;
textPos ← textPos + textLenStack[stackPtr];
patternPosStack[stackPtr] ← patternPos;
patternPos ← patternPos + 1
};
ENDCASE => { -- check next character from text
boundary: BOOLFALSE;
IF textPos IN [start..end) THEN {char ← GetChar[textPos]}
ELSE {
char ← TextFindPrivate.rightBoundaryPattern;
IF patternChar # TextFindPrivate.rightBoundaryPattern THEN boundary ← TRUE;
};
IF NOT boundary AND patternChar = TextFindPrivate.oneCharPattern AND
(patternLooks = NIL OR LooksMatch[textPos,patternPos]) THEN {
IF patternPos # 0 AND patternPos = patternAnchor THEN {
first char(s) of * segment
patternAnchor ← patternAnchor + 1; textAnchor ← textPos + 1
};
patternPos ← patternPos + 1; textPos ← textPos + 1
}
ELSE {
IF NOT boundary AND (SELECT patternChar FROM
char => TRUE, -- this also takes care of rightBoundaryPattern
TextFindPrivate.oneNonAlphaPattern => RopeEdit.GetCharProp[char] # alphaNumeric,
TextFindPrivate.oneAlphaPattern => RopeEdit.GetCharProp[char] = alphaNumeric,
TextFindPrivate.oneNonBlankPattern => RopeEdit.GetCharProp[char] # white,
TextFindPrivate.oneBlankPattern => RopeEdit.GetCharProp[char] = white,
TextFindPrivate.oneCharPattern => FALSE, -- known from above that looks don't match
>= TextFindPrivate.EightBit => -- check both upper and lower case
SELECT patternChar ← CharBits[patternChar] FROM
IN ['A..'Z] => patternChar = RopeEdit.UpperCase[char],
IN ['a ..'z] => patternChar = RopeEdit.LowerCase[char],
ENDCASE => patternChar = char,
ENDCASE => FALSE)
AND (patternLooks=NIL OR LooksMatch[textPos,patternPos])
THEN -- chars match -- {
patternPos ← patternPos + 1; textPos ← textPos + 1
}
ELSE { -- chars don't match; try to change some wild card position
WHILE stackPtr # 0 DO
txtpos: INT ← textPosStack[stackPtr];
txtlen: INT ← textLenStack[stackPtr];
IF interrupt#NIL AND interrupt^ THEN GO TO Return;
IF txtlen < 0 THEN { -- this is an incrementing wildcard
ppos: NAT;
boundary ← FALSE;
IF txtpos IN [start..end) THEN {
charType ← RopeEdit.GetCharProp[GetChar[txtpos]];
}
ELSE {
boundary ← TRUE
};
IF NOT boundary AND
(SELECT TextFindPrivate.Pat[patternArray[ppos←patternPosStack[stackPtr]]] FROM
TextFindPrivate.anyNonAlphaPat => charType # alphaNumeric,
TextFindPrivate.anyAlphaPat => charType = alphaNumeric,
TextFindPrivate.anyNonBlankPat => charType # white,
TextFindPrivate.anyBlankPat => charType = white,
TextFindPrivate.anyStringPat => TRUE,
ENDCASE => ERROR)
AND (patternLooks=NIL OR LooksMatch[txtpos,ppos]) THEN {
patternPos ← ppos + 1;
textPos ← textPosStack[stackPtr] ← txtpos + 1;
EXIT
}
}
ELSE IF txtlen > 0 THEN { -- this is a decrementing wildcard
patternPos ← patternPosStack[stackPtr] + 1;
textPos ← textPosStack[stackPtr] + txtlen - 1;
textLenStack[stackPtr] ← txtlen - 1;
EXIT
}
ELSE NULL; -- decrementing wildcard with no place left to go
stackPtr ← stackPtr - 1;
ENDLOOP;
IF stackPtr = 0 THEN -- failed to match a stacked wild card
IF patternAnchor > 0 AND textAnchor < end THEN {
there was a * with no looks, so can advance it
patternPos ← patternAnchor;
textPos ← textAnchor ← textAnchor + 1
}
ELSE EXIT --start matching over at next text location--
}
}
}
};
ENDCASE;
ENDLOOP; -- end of pattern loop
at ← at+1; -- start over with next character
ENDLOOP; -- end of text loop
EXITS Return => NULL;
}; -- of Try --
IsWord: PUBLIC PROC [rope: ROPE, at, atEnd: INT] RETURNS [BOOL] = {
IF at > 0 AND
RopeEdit.GetCharProp[Rope.Fetch[rope,at-1]] = alphaNumeric THEN RETURN [FALSE];
IF atEnd < Rope.Size[rope] AND
RopeEdit.GetCharProp[Rope.Fetch[rope,atEnd]] = alphaNumeric THEN RETURN [FALSE];
RETURN [TRUE];
};
SearchParts: PROC [finder: Finder, rope: ROPE, runs: Runs, start: INT, len: INT,
looksExact: BOOL, interrupt: REF BOOLNIL] RETURNS [found: BOOL, at, atEnd, before, after: INT] = {
IF finder.wordSearch THEN DO -- repeat search until find a word
[found, at, atEnd, before, after] ← TryToFind[finder, rope, runs, start, len, looksExact, interrupt];
IF NOT found OR (interrupt#NIL AND interrupt^) THEN RETURN; -- failed
IF IsWord[rope, at, atEnd] THEN RETURN; -- got it
start ← after; -- try again
ENDLOOP;
[found, at, atEnd, before, after] ←
TryToFind[finder, rope, runs, start, len, looksExact, interrupt]
};
SearchText: PUBLIC PROC [finder: Finder, text: Text, looksExact: BOOLFALSE,
direction: SearchDirection ← forward, interrupt: REF BOOLNIL
] RETURNS [found: BOOL, at, atEnd, before, after: INT] = {
SELECT direction FROM
forward => RETURN SearchParts[finder, GetRope[text.node], GetRuns[text.node], text.start, text.len, looksExact, interrupt];
backward => RETURN SearchPartsBackward[finder, GetRope[text.node], GetRuns[text.node], text.start, text.len, looksExact, interrupt];
ENDCASE => ERROR;
};
SearchRope: PUBLIC PROC [finder: Finder, rope: ROPE, start: INT ← 0, len: INT ← maxLen,
direction: SearchDirection ← forward, interrupt: REF BOOLNIL
] RETURNS [found: BOOL, at, atEnd, before, after: INT] = {
SELECT direction FROM
forward => RETURN SearchParts[finder, rope, NIL, start, len, FALSE, interrupt];
backward => RETURN SearchPartsBackward[finder, rope, NIL, start, len, FALSE, interrupt];
ENDCASE => ERROR;
};
TextFind3Impl
SearchPartsBackward: PROC [finder: Finder, rope: ROPE, runs: Runs,
start: INT, len: INT, looksExact: BOOL, interrupt: REF BOOLNIL]
RETURNS [found: BOOL, at, atEnd, before, after: INT] = {
IF finder.wordSearch THEN DO -- repeat search until find a word
[found, at, atEnd, before, after] ← TryToFindBackwards[finder, rope, runs, start, len, looksExact];
IF NOT found OR (interrupt#NIL AND interrupt^) THEN RETURN; -- failed
IF IsWord[rope, at, atEnd] THEN RETURN; -- got it
len ← before-start; -- try again
ENDLOOP;
[found, at, atEnd, before, after] ←
TryToFindBackwards[finder, rope, runs, start, len, looksExact, interrupt]
};
TryToFindBackwards: PROC [finder: Finder, rope: ROPE, runs: Runs, start: INT ← 0, len: INT ← maxLen, looksExact: BOOLFALSE, interrupt: REF BOOLNIL] RETURNS [found: BOOL, at, atEnd, before, after: INT] = {
patternPosStack: REF TextFindPrivate.PatternStackArray ~ finder.patternPosStack;
textPosStack: REF TextFindPrivate.TextStackArray ~ finder.textPosStack;
textLenStack: REF TextFindPrivate.TextStackArray ~ finder.textLenStack;
patternArray: REF TextFindPrivate.PatternArray ~ finder.patternArray;
length: NAT ~ finder.length;
patternLooks: REF TextFindPrivate.LooksArray ~ finder.patternLooks;
nameArray: REF TextFindPrivate.NameArray ~ finder.nameArray;
lastPatChar1: CHAR ~ finder.lastPatChar1;
lastPatChar2: CHAR ~ finder.lastPatChar2;
ropeReader: RopeReader.Ref ~ finder.ropeReader;
lksReader: LooksReader.Ref ~ finder.lksReader;
looks: Looks ~ finder.looks;
looksOnly: BOOL ~ finder.looksOnly;
leftBracketSeen: BOOL ← finder.leftBracketSeen;
lastPatternCharIsNormal: BOOL ~ finder.lastPatternCharIsNormal;
stackPtr, patternPos, patternAnchor, patternFirst: NAT ← 0;
char, patternChar: CHAR ← 377C;
charType: RopeEdit.CharProperty;
beginPos, endPos, textPos, textAnchor, end, size: INT;
psLength: NAT;
LooksMatch: PROC [txtpos: INT, ppos: NAT] RETURNS [BOOL] = {
patlks, sourcelks: Looks;
IF (patlks ← patternLooks[ppos-1]) = noLooks THEN RETURN [TRUE];
IF runs=NIL THEN RETURN [FALSE]; -- pattern has looks and text doesn't
IF txtpos NOT IN (start..end] THEN RETURN [FALSE]; -- boundary char has no looks
LooksReader.SetPosition[lksReader,runs,txtpos];
sourcelks ← LooksReader.Backwards[lksReader];
RETURN[patlks=(IF looksExact THEN sourcelks ELSE TextLooks.LooksAND[sourcelks,patlks])]
};
GetChar: PROC [txtpos: INT] RETURNS [char: CHAR] = {
SELECT txtpos FROM
IN (start..end] => { -- read the character from the rope
char ← Rope.Fetch[rope, txtpos-1];
};
ENDCASE => ERROR;
};
PropTest: TYPE = { eq, ne, any };
MaxCount: PROC [propTest: PropTest, property: RopeEdit.CharProperty ← illegal]
RETURNS [count: INT] = {
count ← 0;
DO
IF textPos-count NOT IN (start..end] THEN EXIT;
char ← GetChar[textPos-count];
IF propTest=eq THEN IF RopeEdit.GetCharProp[char] # property THEN EXIT ELSE NULL
ELSE IF propTest=ne AND RopeEdit.GetCharProp[char]=property THEN EXIT ELSE NULL;
IF patternLooks # NIL AND patternLooks[patternPos] # noLooks
AND NOT LooksMatch[textPos-count, patternPos] THEN EXIT;
count ← count+1;
ENDLOOP;
};
size ← Rope.Size[rope];
start ← MIN[MAX[0,start],size];
len ← MIN[MAX[0,len],size-start];
end ← start+len;
found ← FALSE;
atEnd ← end;
IF looksOnly THEN {
looksReader: LooksReader.Ref ~ LooksReader.GetLooksReader[];
LooksReader.SetPosition[looksReader, runs, end];
atEnd ← end;
WHILE atEnd > start DO
lks: Looks ← LooksReader.Backwards[looksReader];
IF NOT looksExact THEN lks ← TextLooks.LooksAND[lks, looks];
IF lks = looks THEN EXIT; -- have found a match
atEnd ← atEnd-1;
ENDLOOP;
LooksReader.FreeLooksReader[looksReader];
IF atEnd <= start THEN RETURN; -- failed to find a match
RETURN [TRUE, atEnd-1, atEnd, atEnd-1, atEnd]
};
psLength ← length;
UNTIL patternFirst = psLength DO -- discard leading "any's"
SELECT TextFindPrivate.Pat[patternArray[patternFirst]] FROM
TextFindPrivate.anyStringPat, TextFindPrivate.anyAlphaPat, TextFindPrivate.anyNonAlphaPat, TextFindPrivate.anyBlankPat, TextFindPrivate.anyNonBlankPat => NULL;
ENDCASE => EXIT;
patternFirst ← patternFirst+1;
ENDLOOP;
IF patternFirst = psLength THEN RETURN [TRUE, end, end, end, end]; -- null pattern
DO -- text loop
IF lastPatternCharIsNormal THEN {
IF lastPatChar1 = TextFindPrivate.rightBoundaryPattern THEN {
IF atEnd < size THEN RETURN; -- failed since not at end of node
patternPos ← psLength-1; textPos ← atEnd
}
ELSE { -- search for next instance of last pattern char
atEnd ← MIN[end,atEnd];
RopeReader.SetPosition[ropeReader, rope, atEnd];
UNTIL atEnd <= start DO
SELECT RopeReader.Backwards[ropeReader] FROM
lastPatChar1, lastPatChar2 =>
IF patternLooks=NIL OR LooksMatch[atEnd, psLength] THEN EXIT;
ENDCASE;
atEnd ← atEnd-1;
ENDLOOP;
patternPos ← psLength-1; textPos ← atEnd - 1
}
}
ELSE { patternPos ← psLength; textPos ← atEnd };
IF atEnd <= start THEN EXIT;
stackPtr ← 0;
patternAnchor ← psLength;
after ← endPos ← textAnchor ← atEnd;
DO -- pattern loop
IF patternPos <= patternFirst THEN { -- have finished pattern
found ← TRUE;
textPos ← MAX[start,textPos]; -- in case used initial boundary char in making the match
atEnd ← MIN[end,endPos]; -- in case used final boundary char in making the match
after ← MIN[after,end]; -- in case used final boundary char in making the match
at ← IF leftBracketSeen THEN beginPos ELSE textPos;
before ← textPos;
GO TO Return
};
IF interrupt#NIL AND interrupt^ THEN GO TO Return;
WITH p:patternArray[patternPos-1] SELECT FROM
startname => { nameArray[p.index].at ← textPos; patternPos ← patternPos-1 };
endname => { nameArray[p.index].atEnd ← textPos; patternPos ← patternPos-1 };
not => { -- check that next character is not the one in this pattern element
IF textPos NOT IN (start..end] THEN EXIT;
char ← GetChar[textPos];
SELECT patternChar ← p.char FROM
char => IF patternLooks=NIL OR LooksMatch[textPos,patternPos] THEN EXIT;
>= TextFindPrivate.EightBit => { -- check both upper and lower case
IF (SELECT patternChar ← CharBits[patternChar] FROM
IN ['A..'Z] => patternChar = RopeEdit.UpperCase[char],
IN ['a ..'z] => patternChar = RopeEdit.LowerCase[char],
ENDCASE => patternChar = char)
AND (patternLooks=NIL OR LooksMatch[textPos,patternPos])
THEN EXIT
}; -- chars match
ENDCASE;
patternPos ← patternPos-1; textPos ← textPos - 1
};
pattern => { SELECT patternChar ← p.char FROM
TextFindPrivate.leftBracketPattern => { beginPos ← textPos; patternPos ← patternPos-1 };
TextFindPrivate.rightBracketPattern => { endPos ← textPos; patternPos ← patternPos-1 };
TextFindPrivate.nopPattern => patternPos ← patternPos-1;
TextFindPrivate.anyStringPattern => {
IF patternLooks # NIL AND
patternLooks[patternPos-1] # noLooks THEN {
stackPtr ← stackPtr + 1;
textPosStack[stackPtr] ← textPos;
patternPosStack[stackPtr] ← patternPos
}
ELSE { textAnchor ← textPos; patternAnchor ← patternPos - 1; stackPtr ← 0 };
patternPos ← patternPos - 1
};
TextFindPrivate.anyNonAlphaPattern, TextFindPrivate.anyAlphaPattern, TextFindPrivate.anyNonBlankPattern, TextFindPrivate.anyBlankPattern => {
stackPtr ← stackPtr + 1;
textPosStack[stackPtr] ← textPos;
patternPosStack[stackPtr] ← patternPos;
patternPos ← patternPos - 1
};
TextFindPrivate.maxStringPattern => {
IF patternLooks # NIL AND patternLooks[patternPos] # noLooks THEN {
stackPtr ← stackPtr + 1;
textPosStack[stackPtr] ← textPos;
textLenStack[stackPtr] ← MaxCount[any];
patternPosStack[stackPtr] ← patternPos
}
ELSE {
stackPtr ← 1;
textPosStack[stackPtr] ← textPos;
textLenStack[stackPtr] ← textPos-start;
patternPosStack[stackPtr] ← patternPos
};
textPos ← textPos - textLenStack[stackPtr];
patternPos ← patternPos - 1
};
TextFindPrivate.maxNonAlphaPattern, TextFindPrivate.maxAlphaPattern, TextFindPrivate.maxNonBlankPattern, TextFindPrivate.maxBlankPattern => {
stackPtr ← stackPtr + 1;
textPosStack[stackPtr] ← textPos;
textLenStack[stackPtr] ← SELECT patternChar FROM
TextFindPrivate.maxNonAlphaPattern => MaxCount[ne, alphaNumeric],
TextFindPrivate.maxAlphaPattern => MaxCount[eq, alphaNumeric],
TextFindPrivate.maxNonBlankPattern => MaxCount[ne, white],
TextFindPrivate.maxBlankPattern => MaxCount[eq, white],
ENDCASE => ERROR;
textPos ← textPos - textLenStack[stackPtr];
patternPosStack[stackPtr] ← patternPos;
patternPos ← patternPos - 1
};
ENDCASE => { -- check next character from text
boundary: BOOLFALSE;
IF textPos IN (start..end] THEN {char ← GetChar[textPos]}
ELSE {
char ← TextFindPrivate.leftBoundaryPattern;
IF patternChar # TextFindPrivate.leftBoundaryPattern THEN boundary ← TRUE;
};
IF NOT boundary AND patternChar = TextFindPrivate.oneCharPattern AND
(patternLooks = NIL OR LooksMatch[textPos,patternPos]) THEN {
IF patternPos # psLength AND patternPos = patternAnchor THEN { -- first char(s) of * segment
patternAnchor ← patternAnchor - 1; textAnchor ← textPos - 1
};
patternPos ← patternPos - 1; textPos ← textPos - 1
}
ELSE {
IF NOT boundary AND (SELECT patternChar FROM
char => TRUE, -- this also takes care of leftBoundaryPattern
TextFindPrivate.oneNonAlphaPattern => RopeEdit.GetCharProp[char] # alphaNumeric,
TextFindPrivate.oneAlphaPattern => RopeEdit.GetCharProp[char] = alphaNumeric,
TextFindPrivate.oneNonBlankPattern => RopeEdit.GetCharProp[char] # white,
TextFindPrivate.oneBlankPattern => RopeEdit.GetCharProp[char] = white,
TextFindPrivate.oneCharPattern => FALSE, -- known from above that looks don't match
>= TextFindPrivate.EightBit => -- check both upper and lower case
SELECT patternChar ← CharBits[patternChar] FROM
IN ['A..'Z] => patternChar = RopeEdit.UpperCase[char],
IN ['a ..'z] => patternChar = RopeEdit.LowerCase[char],
ENDCASE => patternChar = char,
ENDCASE => FALSE)
AND (patternLooks=NIL OR LooksMatch[textPos,patternPos])
THEN -- chars match -- { patternPos ← patternPos - 1; textPos ← textPos - 1 }
ELSE { -- chars don't match; try to increment some wild card position
WHILE stackPtr # 0 DO
txtpos: INT ← textPosStack[stackPtr];
txtlen: INT ← textLenStack[stackPtr];
IF interrupt#NIL AND interrupt^ THEN GO TO Return;
IF txtlen < 0 THEN { -- this is an incrementing wildcard
boundary: BOOLFALSE;
ppos: NAT;
IF txtpos IN (start..end] THEN {
charType ← RopeEdit.GetCharProp[GetChar[txtpos]]
}
ELSE {
boundary ← TRUE;
};
IF NOT boundary AND
(SELECT TextFindPrivate.Pat[patternArray[(ppos←patternPosStack[stackPtr])-1]] FROM
TextFindPrivate.anyNonAlphaPat => charType # alphaNumeric,
TextFindPrivate.anyAlphaPat => charType = alphaNumeric,
TextFindPrivate.anyNonBlankPat => charType # white,
TextFindPrivate.anyBlankPat => charType = white,
TextFindPrivate.anyStringPat => TRUE,
ENDCASE => ERROR) AND
(patternLooks=NIL OR LooksMatch[txtpos,ppos]) THEN {
patternPos ← ppos - 1;
textPos ← textPosStack[stackPtr] ← txtpos - 1;
EXIT
}
}
ELSE IF txtlen > 0 THEN { -- this is a decrementing wildcard
patternPos ← patternPosStack[stackPtr] - 1;
textPos ← textPosStack[stackPtr] - txtlen + 1;
textLenStack[stackPtr] ← txtlen - 1;
EXIT
}
ELSE NULL; -- decrementing wildcard with no place left to go
stackPtr ← stackPtr - 1;
ENDLOOP;
IF stackPtr = 0 THEN -- failed to match a stacked wild card
IF patternAnchor < psLength AND textAnchor > start THEN {
there was a * with no looks, so can advance it
patternPos ← patternAnchor; textPos ← textAnchor ← textAnchor - 1
}
ELSE EXIT --start matching over at next text location--
}
}
}
};
ENDCASE;
ENDLOOP; -- end of pattern loop
atEnd ← atEnd-1; -- start over with next character
ENDLOOP; -- end of text loop
EXITS Return => NULL;
};
TreeFindImpl
Search: PUBLIC PROC [finder: Finder, span: Span, looksExact: BOOLFALSE,
commentControl: CommentControl ← includeComments,
checkFormat: BOOLFALSE, format: ATOMNIL,
checkStyle: BOOLFALSE, style: ATOMNIL,
styleProc: PROC [Node] RETURNS [ATOM] ← NIL,
direction: SearchDirection ← forward, interrupt: REF BOOLNIL
] RETURNS [found: BOOLFALSE, where: Node, at, atEnd, before, after: INT ← 0] = {
node, last, parent: Node ← NIL;
SELECT direction FROM
forward => { node ← span.start.node; last ← span.end.node };
backward => { node ← span.end.node; last ← span.start.node };
ENDCASE => ERROR;
IF node=NIL OR last=NIL THEN RETURN;
IF span.start.where=nodeItself THEN span.start.where ← 0;
IF span.end.where=nodeItself THEN span.end.where ← maxLen;
DO -- test new node each time through the loop
SELECT TRUE FROM
checkFormat AND node.formatName#format => NULL;
checkStyle AND styleProc[node]#style => NULL;
(SELECT commentControl FROM includeComments => FALSE,
excludeComments => node.comment, commentsOnly => NOT node.comment, ENDCASE => ERROR) => NULL;
ENDCASE => {
IF finder#NIL THEN {
start: INT ~ IF node=span.start.node THEN span.start.where ELSE 0;
end: INT ~ IF node=span.end.node THEN span.end.where ELSE maxLen;
[found: found, at: at, atEnd: atEnd, before: before, after: after] ← SearchText[finder: finder, text: [node, start, end-start], looksExact: looksExact, direction: direction, interrupt: interrupt];
}
ELSE { found ← TRUE; before ← at ← 0; atEnd ← after ← Size[node] };
IF found THEN { where ← node; RETURN };
};
go to next one
IF interrupt#NIL AND interrupt^ THEN EXIT;
IF node=last THEN RETURN;
SELECT direction FROM
forward => node ← StepForward[node];
backward => [node, parent] ← Backward[node, parent];
ENDCASE => ERROR;
IF node=NIL THEN RETURN;
ENDLOOP;
};
SearchApply: PUBLIC PROC [finder: Finder, span: Span, proc: SearchApplyProc,
looksExact: BOOLFALSE, commentControl: CommentControl ← includeComments,
checkFormat: BOOLFALSE, format: ATOMNIL,
checkStyle: BOOLFALSE, style: ATOMNIL,
styleProc: PROC [Node] RETURNS [ATOM] ← NIL
] RETURNS [count: INT ← 0] = {
where, node, last: Node;
start, lastLen, at, atEnd, before, after, from, delta: INT;
found, continue, bumpCount: BOOL;
IF span.start.where=nodeItself THEN span.start.where ← 0;
IF span.end.where=nodeItself THEN span.end.where ← maxLen;
node ← span.start.node;
start ← span.start.where;
last ← span.end.node;
lastLen ← span.end.where;
UNTIL node=NIL DO
IF node=last AND start >= lastLen THEN RETURN;
[found, where, at, atEnd, before, after] ← Search[finder, [[node, start], [last, lastLen]], looksExact, commentControl, checkFormat, format, checkStyle, style, styleProc, forward];
IF NOT found THEN RETURN;
[continue, bumpCount, from, delta] ← proc[where, at, atEnd, before, after];
IF bumpCount THEN count ← count+1;
IF NOT continue THEN RETURN;
IF where=last THEN lastLen ← lastLen+MIN[delta, maxLen-lastLen];
IF finder#NIL THEN { node ← where; start ← from }
ELSE { node ← StepForward[node]; start ← 0 };
ENDLOOP;
};
END.