<> <> <> <> <> <<>> DIRECTORY TextFind, TextFindPrivate, TextLooks, TextLooksSupport, TextEdit, TextNode, RopeEdit, Rope, RopeReader, RunReader, LooksReader; TextFindImpl: CEDAR PROGRAM IMPORTS TextEdit, TextLooks, TextLooksSupport, LooksReader, RopeEdit, RopeReader, TextNode, RunReader, Rope EXPORTS TextFind = BEGIN OPEN TextFind, TextFindPrivate, RopeEdit; MalformedPattern: PUBLIC ERROR [ec:PatternErrorCode] = CODE; Finder: TYPE = REF FinderRec; FinderRec: PUBLIC TYPE = FinderRecord; <<-- ***** Operations *****>> NameLoc: PUBLIC PROC [finder: Finder, name: ROPE] RETURNS [at, atEnd: Offset] = { OPEN finder; at _ atEnd _ 0; IF finder = NIL OR 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: TextLooks.Looks] = { OPEN finder; looks _ TextLooks.noLooks; IF finder = NIL OR 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; }; Create: PUBLIC PROC [pattern: RefTextNode, literal, word, ignoreLooks, ignoreCase, addBounds: BOOLEAN _ FALSE, patternStart: Offset _ 0, patternLen: Offset _ MaxLen] 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: BOOLEAN _ FALSE, patternStart: Offset _ 0, patternLen: Offset _ MaxLen] 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: BOOLEAN _ FALSE, patternStart: Offset _ 0, patternLen: Offset _ MaxLen] RETURNS [finder: Finder] = BEGIN NewLooks: PROC [num: NAT] RETURNS [array: REF LooksArray] = { array _ TextNode.pZone.NEW[LooksArray[num]]; FOR i:NAT IN [0..num) DO array[i] _ TextLooks.noLooks; ENDLOOP }; char, patternChar: CHAR _ 377C; pLen: Offset; patternLength, plen, psIndex, nameCount: NAT _ 0; nameList: LIST OF Rope.ROPE; -- in reverse order of appearance nameLooksList: LIST OF TextLooks.Looks; nameLooks: TextLooks.Looks; insideNamedPat: BOOLEAN _ 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: SAFE PROC [c: CHAR] RETURNS [stop: BOOL] = TRUSTED { IF ~BlankChar[c] AND ~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]) > MaxPatternLength THEN ERROR MalformedPattern[toobig]; patternLength _ plen _ patternLen; finder _ TextNode.pZone.NEW[FinderRec]; BEGIN OPEN finder; PatternProc: TYPE = PROC [char: CHAR, looks: TextLooks.Looks, ignoreCase: BOOLEAN]; patProc: PatternProc = IF literal THEN LitChar ELSE PatChar; GetLooks: PROC RETURNS [lks: TextLooks.Looks] = { RETURN [IF lksReader = NIL THEN TextLooks.noLooks ELSE LooksReader.Get[lksReader ! RunReader.NoMoreRuns => { lks _ TextLooks.noLooks; CONTINUE }]] }; LitChar: PatternProc = TRUSTED { IF looks # TextLooks.noLooks THEN { IF patternLooks = NIL THEN patternLooks _ NewLooks[patternLength]; patternLooks[psIndex] _ looks }; IF ignoreCase AND (char IN ['A..'Z] OR char IN ['a..'z]) THEN { patternArray[psIndex] _ [pattern[char+200B]]; <<-- 200B tells matcher to check both upper and lower>> IF psIndex = 0 THEN { firstPatternCharIsNormal _ TRUE; firstPatChar1 _ UpperCase[char]; firstPatChar2 _ LowerCase[char] }; IF psIndex = patternLength-1 THEN { lastPatternCharIsNormal _ TRUE; lastPatChar1 _ UpperCase[char]; lastPatChar2 _ LowerCase[char] }} ELSE { patternArray[psIndex] _ [pattern[char]]; IF psIndex = patternLength-1 THEN { lastPatternCharIsNormal _ TRUE; lastPatChar1 _ char; lastPatChar2 _ 0C }; IF psIndex = 0 THEN { firstPatternCharIsNormal _ TRUE; firstPatChar1 _ char; firstPatChar2 _ 0C }}}; NotChar: PatternProc = TRUSTED { IF looks # TextLooks.noLooks THEN { IF patternLooks = NIL THEN patternLooks _ NewLooks[patternLength]; patternLooks[psIndex] _ looks }; IF ignoreCase THEN patternArray[psIndex] _ [not[char+200B]] ELSE patternArray[psIndex] _ [not[char]]; }; PatChar: PatternProc = TRUSTED { IF looks # TextLooks.noLooks AND patternLooks = NIL THEN patternLooks _ NewLooks[patternLength]; IF patternLooks # NIL THEN patternLooks[psIndex] _ looks; SELECT char FROM '' => { IF RopeReader.GetIndex[ropeReader] >= plen THEN ERROR MalformedPattern[endquote]; patternLength _ patternLength-1; LitChar[RopeReader.Get[ropeReader],GetLooks[],FALSE] }; IN ['A .. 'Z], IN ['a .. 'z] => LitChar[char,looks,ignoreCase]; '~ => { IF RopeReader.GetIndex[ropeReader] >= plen THEN ERROR MalformedPattern[endtilda]; patternLength _ patternLength-1; char _ RopeReader.Get[ropeReader]; looks _ GetLooks[]; SELECT char FROM '' => { IF RopeReader.GetIndex[ropeReader] >= plen THEN ERROR MalformedPattern[endquote]; patternLength _ patternLength-1; NotChar[RopeReader.Get[ropeReader],GetLooks[],FALSE] }; IN ['A .. 'Z], IN ['a .. 'z] => NotChar[char,looks,ignoreCase]; '% => patternArray[psIndex] _ [pattern[oneNonBlankPattern]]; '$ => IF psIndex > 0 AND patternArray[psIndex-1]=[pattern[anyNonBlankPattern]] AND (patternLooks=NIL OR patternLooks[psIndex-1]=looks) THEN { -- change to max patternLength _ patternLength-1; psIndex _ psIndex-1; patternArray[psIndex] _ [pattern[maxNonBlankPattern]] } ELSE { -- new entry patternArray[psIndex] _ [pattern[anyNonBlankPattern]]; stackSize _ stackSize+1 }; '@ => patternArray[psIndex] _ [pattern[oneNonAlphaPattern]]; '& => IF psIndex > 0 AND patternArray[psIndex-1]=[pattern[anyNonAlphaPattern]] AND (patternLooks=NIL OR patternLooks[psIndex-1]=looks) THEN { -- change to max patternLength _ patternLength-1; psIndex _ psIndex-1; patternArray[psIndex] _ [pattern[maxNonAlphaPattern]] } ELSE { -- new entry patternArray[psIndex] _ [pattern[anyNonAlphaPattern]]; stackSize _ stackSize+1 }; ENDCASE => patternArray[psIndex] _ [not[char]]; }; '# => patternArray[psIndex] _ [pattern[oneCharPattern]]; '* => IF psIndex > 0 AND patternArray[psIndex-1]=[pattern[anyStringPattern]] AND (patternLooks=NIL OR patternLooks[psIndex-1]=looks) THEN { -- change to max psIndex _ psIndex-1; patternLength _ patternLength-1; stackSize _ MAX[1, stackSize]; patternArray[psIndex] _ [pattern[maxStringPattern]] } ELSE { -- new entry patternArray[psIndex] _ [pattern[anyStringPattern]]; IF looks # TextLooks.noLooks THEN stackSize _ stackSize+1 }; '% => patternArray[psIndex] _ [pattern[oneBlankPattern]]; '$ => IF psIndex > 0 AND patternArray[psIndex-1]=[pattern[anyBlankPattern]] AND (patternLooks=NIL OR patternLooks[psIndex-1]=looks) THEN { -- change to max patternLength _ patternLength-1; psIndex _ psIndex-1; patternArray[psIndex] _ [pattern[maxBlankPattern]] } ELSE { -- new entry patternArray[psIndex] _ [pattern[anyBlankPattern]]; stackSize _ stackSize+1 }; '@ => patternArray[psIndex] _ [pattern[oneAlphaPattern]]; '& => IF psIndex > 0 AND patternArray[psIndex-1]=[pattern[anyAlphaPattern]] AND (patternLooks=NIL OR patternLooks[psIndex-1]=looks) THEN { -- change to max patternLength _ patternLength-1; psIndex _ psIndex-1; patternArray[psIndex] _ [pattern[maxAlphaPattern]] } ELSE { -- new entry patternArray[psIndex] _ [pattern[anyAlphaPattern]]; stackSize _ stackSize+1 }; '| => { patternArray[psIndex] _ [pattern[IF psIndex = 0 THEN leftBoundaryPattern ELSE rightBoundaryPattern]]; IF psIndex # 0 AND psIndex # patternLength-1 THEN ERROR MalformedPattern[boundary]; IF psIndex = patternLength-1 THEN { -- right boundary lastPatternCharIsNormal _ TRUE; lastPatChar1 _ rightBoundaryPattern }; IF psIndex = 0 THEN { --left boundary firstPatternCharIsNormal _ TRUE; firstPatChar1 _ leftBoundaryPattern }}; '< => { nameStart: Offset _ RopeReader.GetIndex[ropeReader]; -- index of char after the < nameLen: Offset _ 0; IF insideNamedPat THEN ERROR MalformedPattern[missingNameEnd]; insideNamedPat _ TRUE; nameLooks _ looks; -- remember the looks of the < patternArray[psIndex] _ [startname[nameCount]]; DO SELECT RopeReader.Peek[ropeReader ! RopeReader.ReadOffEnd => GOTO BadName] FROM -- scan to end of name ': => { -- pattern follows [] _ RopeReader.Get[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[ropeReader]; [] _ GetLooks[] }; ENDLOOP; nameList _ TextNode.pZone.CONS[Rope.Substr[patternRope,nameStart,nameLen],nameList]; nameLooksList _ TextNode.pZone.CONS[nameLooks,nameLooksList]; EXITS BadName => ERROR MalformedPattern[missingNameEnd] }; '> => { IF ~insideNamedPat THEN ERROR MalformedPattern[unmatchedNameEnd]; insideNamedPat _ FALSE; patternArray[psIndex] _ [endname[nameCount]]; nameCount _ nameCount+1 }; '{ => { leftBracketSeen _ TRUE; patternArray[psIndex] _ [pattern[leftBracketPattern]] }; '} => { IF rightBracketSeen THEN patternArray[psIndex] _ [pattern[nopPattern]] -- use first } in pattern ELSE { rightBracketSeen _ TRUE; patternArray[psIndex] _ [pattern[rightBracketPattern]] }}; ENDCASE => { patternArray[psIndex] _ [pattern[char]]; IF psIndex = patternLength-1 THEN { lastPatternCharIsNormal _ TRUE; lastPatChar1 _ char; lastPatChar2 _ 0C }; IF psIndex = 0 THEN { firstPatternCharIsNormal _ TRUE; firstPatChar1 _ char; firstPatChar2 _ 0C }}}; -- end of PatChar IF word THEN 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 looks _ TextLooks.FetchLooks[patternRuns,patternStart]; looksOnly _ TRUE; runReader _ RunReader.Create[]; RETURN }; patternArray _ TextNode.pZone.NEW[PatternArray[patternLength]]; ropeReader _ RopeReader.Create[]; RopeReader.SetPosition[ropeReader,patternRope,patternStart]; IF patternRuns # NIL AND ~ignoreLooks THEN { lksReader _ LooksReader.Create[]; LooksReader.SetPosition[lksReader,patternRuns,patternStart] }; psIndex _ 0; DO -- unpack the pattern char: CHAR _ RopeReader.Get[ropeReader ! RopeReader.ReadOffEnd => EXIT]; patProc[char,GetLooks[],ignoreCase]; psIndex _ psIndex + 1; ENDLOOP; length _ patternLength; IF stackSize > 0 THEN { stackSize _ stackSize+1; textPosStack _ TextNode.pZone.NEW[TextStackArray[stackSize]]; textLenStack _ TextNode.pZone.NEW[TextStackArray[stackSize]]; patternPosStack _ TextNode.pZone.NEW[PatternStackArray[stackSize]] }; IF nameList # NIL THEN { nameArray _ TextNode.pZone.NEW[NameArray[nameCount]]; FOR i:NAT DECREASING IN [0..nameCount) DO nameArray[i].name _ nameList.first; nameArray[i].looks _ nameLooksList.first; nameList _ nameList.rest; nameLooksList _ nameLooksList.rest; ENDLOOP }; END; -- of OPEN finder END; -- of Create Start: PUBLIC PROC = {}; END.