SearchRopeBackwards:
PUBLIC
PROC [finder: Finder, rope:
ROPE,
start: INT ← 0, len: INT ← maxLen, interrupt: REF BOOL ← NIL]
RETURNS [found: BOOL, at, atEnd, before, after: INT] = {
[found, at, atEnd, before, after] ←
SearchBackwards[finder, rope, NIL, start, len, FALSE, interrupt]
};
SearchBackwards:
PROC [finder: Finder, rope:
ROPE, runs: TextLooks.Runs,
start: INT, len: INT, looksExact: BOOL, interrupt: REF BOOL ← NIL]
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 TextFindPrivate.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: TextLooks.Runs, start:
INT ← 0, len:
INT ← maxLen, looksExact:
BOOL ←
FALSE, interrupt:
REF
BOOL ←
NIL]
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: BOOL ← FALSE;
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: BOOL ← FALSE;
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;
};