<> <> <> <> <> <> <> <> <<>> DIRECTORY LooksReader USING [Create, Get, SetPosition], Rope USING [Cat, Equal, Fetch, FromChar, Map, ROPE, Size, Substr], RopeEdit USING [AlphaNumericChar, BlankChar, LowerCase, UpperCase], RopeReader USING [Create, Get, GetIndex, GetRope, Peek, ReadOffEnd, SetPosition], RunReader USING [Create, NoMoreRuns], TextEdit USING [GetRope, GetRuns], TextFind, TextFindPrivate USING [anyAlphaPattern, anyBlankPattern, anyNonAlphaPattern, anyNonBlankPattern, anyStringPattern, leftBoundaryPattern, leftBracketPattern, LooksArray, maxAlphaPattern, maxBlankPattern, maxNonAlphaPattern, maxNonBlankPattern, MaxPatternLength, maxStringPattern, NameArray, nopPattern, oneAlphaPattern, oneBlankPattern, oneCharPattern, oneNonAlphaPattern, oneNonBlankPattern, PatternArray, PatternStackArray, rightBoundaryPattern, rightBracketPattern, TextStackArray], TextLooks USING [FetchLooks, Looks, noLooks, Runs]; TextFindImpl: CEDAR PROGRAM IMPORTS TextEdit, TextLooks, LooksReader, Rope, RopeEdit, RopeReader, RunReader, TextFind EXPORTS TextFind = { OPEN TextFind; ROPE: TYPE ~ Rope.ROPE; MalformedPattern: PUBLIC ERROR [ec:PatternErrorCode] = CODE; <<>> <<***** Operations *****>> 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 [TextLooks.Looks _ TextLooks.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; }; }; Find: PUBLIC PROC [pattern, text: RefTextNode, literal, word, ignoreLooks, ignoreCase, looksExact, addBounds: BOOL _ FALSE, patternStart: INT _ 0, patternLen: INT _ LAST[INT], textStart: INT _ 0, textLen: INT _ LAST[INT], interrupt: REF BOOL _ NIL] RETURNS [found: BOOL, at, atEnd, before, after: INT] = { [found,at,atEnd,before,after] _ TextFind.Try[Create[pattern,literal,word,ignoreLooks,ignoreCase,addBounds,patternStart,patternLen], text,textStart,textLen,looksExact,interrupt] }; BackwardsFind: PUBLIC PROC [pattern, text: RefTextNode, literal, word, ignoreLooks, ignoreCase, looksExact, addBounds: BOOL _ FALSE, patternStart: INT _ 0, patternLen: INT _ LAST[INT], textStart: INT _ 0, textLen: INT _ LAST[INT], interrupt: REF BOOL _ NIL] RETURNS [found: BOOL, at, atEnd, before, after: INT] = { [found,at,atEnd,before,after] _ TextFind.TryBackwards[ Create[pattern,literal,word,ignoreLooks,ignoreCase,addBounds,patternStart,patternLen], text,textStart,textLen,looksExact,interrupt] }; Create: PUBLIC PROC [pattern: RefTextNode, literal, word, ignoreLooks, ignoreCase, addBounds: BOOL _ FALSE, patternStart: INT _ 0, patternLen: INT _ LAST[INT]] RETURNS [finder: Finder] = { patternRope: ROPE _ TextEdit.GetRope[pattern]; patternRuns: TextLooks.Runs _ TextEdit.GetRuns[pattern]; RETURN [CreateFromParts[patternRope, patternRuns, literal, word, ignoreLooks, ignoreCase, addBounds, patternStart, patternLen]] }; CreateFromRope: PUBLIC PROC [pattern: ROPE, literal, word, ignoreCase, addBounds: BOOL _ FALSE, patternStart: INT _ 0, patternLen: INT _ LAST[INT]] RETURNS [finder: Finder] = { RETURN [CreateFromParts[pattern, NIL, literal, word, TRUE, ignoreCase, addBounds, patternStart, patternLen]] }; CreateFromParts: PROC [patternRope: ROPE, patternRuns: TextLooks.Runs, literal, word, ignoreLooks, ignoreCase, addBounds: BOOL _ FALSE, patternStart: INT _ 0, patternLen: INT _ LAST[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] _ TextLooks.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 TextLooks.Looks; nameLooks: TextLooks.Looks; insideNamedPat: BOOL _ FALSE; 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: TextLooks.Looks, ignoreCase: BOOL]; patProc: PatternProc = IF literal THEN LitChar ELSE PatChar; GetLooks: PROC RETURNS [lks: TextLooks.Looks] = { RETURN [IF finder.lksReader = NIL THEN TextLooks.noLooks ELSE LooksReader.Get[finder.lksReader ! RunReader.NoMoreRuns => { lks _ TextLooks.noLooks; CONTINUE }]] }; LitChar: PatternProc = { IF looks # TextLooks.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 # TextLooks.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 # TextLooks.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 # TextLooks.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 <> 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 }; -- of Create }. <s.>>