-- RopeFindImpl.Mesa
-- derived from EditorFind.Mesa of Laurel 6
-- last writen by Paxton, 3-Jun-81 12:39:07


DIRECTORY
  RopeFind,
  RopeEdit,
  Rope,
  RopeReader,
  Inline;

RopeFindImpl: PROGRAM
  IMPORTS RopeEdit, RopeReader, Rope, Inline
  EXPORTS RopeFind, RopeEdit =

BEGIN
OPEN ropeI:Rope, RopeFind, RopeEdit, rdrI:RopeReader;

MalformedPattern: PUBLIC ERROR = CODE;
LineBreakValue: Char = 200C;
CharMask: Char = 177C;
LegalCharacters: TYPE = Char[0C .. 177C];

TextStackArray: TYPE = RECORD[stack: SEQUENCE length: NAT OF Int];
PatternStackArray: TYPE = RECORD[stack: SEQUENCE length: NAT OF NAT];
anyStringPattern: Char = LineBreakValue;
oneCharPattern: Char = LineBreakValue + 1;
oneAlphaPattern: Char = LineBreakValue + 2;
oneNonAlphaPattern: Char = LineBreakValue + 3;
anyAlphaPattern: Char = LineBreakValue + 4;
anyNonAlphaPattern: Char = LineBreakValue + 5;
leftBracketPattern: Char = LineBreakValue + 6;
rightBracketPattern: Char = LineBreakValue + 7;
MaxPatternLength: NAT = 1024;

Finder: TYPE = REF FinderRec;
FinderRec: PUBLIC TYPE = RECORD [
	stackSize: NAT ← 0,
	patternPosStack: REF PatternStackArray,
	textPosStack: REF TextStackArray,
	patternString: REF TEXT,
	firstPatChar1, firstPatChar2: Char ← 377C,
	reader: rdrI.Ref,
	leftBracketSeen, rightBracketSeen: BOOLEAN ← FALSE,
	firstPatternCharIsNormal: BOOLEAN ← FALSE
	];

Create: PUBLIC PROC [pattern: Rope] RETURNS [finder: Finder] = 
BEGIN
psIndex: NAT ← 0;
char, patternChar: Char ← 377C;
pLen: Int;
patternLength: NAT;

IF (pLen ← ropeI.Size[pattern]) > MaxPatternLength THEN ERROR MalformedPattern;
patternLength ← LOOPHOLE[Inline.LowHalf[pLen]];
finder ← NEW[FinderRec];

-- normalize search string; process special characters.

BEGIN OPEN finder;
i: CARDINAL;
patternString ← NEW[TEXT[patternLength]];
reader ← rdrI.Create[];
rdrI.SetPosition[reader,pattern];
FOR i ← 0, i + 1 UNTIL i >= patternLength DO -- unpack the pattern
  SELECT (char ← rdrI.Get[reader]) FROM
    '' =>
      BEGIN
      IF i + 1 < patternLength THEN i ← i + 1 ELSE ERROR MalformedPattern;
      patternString[psIndex] ← rdrI.Get[reader];
      IF psIndex = 0 THEN {firstPatternCharIsNormal ← TRUE; firstPatChar1 ← patternString[0]};
      END;
    IN ['A .. 'Z], IN ['a .. 'z] =>
      BEGIN
      patternString[psIndex] ← Inline.BITOR[char, LineBreakValue];
      IF psIndex = 0 THEN
        BEGIN
        firstPatternCharIsNormal ← TRUE;
        firstPatChar1 ← UpperCase[char];
        firstPatChar2 ← LowerCase[char];
        END;
      END;
    '# => patternString[psIndex] ← oneCharPattern;
    '* => patternString[psIndex] ← anyStringPattern;
    '@ => patternString[psIndex] ← oneAlphaPattern;
    '! => patternString[psIndex] ← oneNonAlphaPattern;
    '& => { patternString[psIndex] ← anyAlphaPattern; stackSize ← stackSize+1 };
    '~ => { patternString[psIndex] ← anyNonAlphaPattern; stackSize ← stackSize+1 };
    '{ => {
    	IF leftBracketSeen THEN ERROR MalformedPattern ELSE leftBracketSeen ← TRUE;
	patternString[psIndex] ← leftBracketPattern};
    '} => {
    	IF rightBracketSeen THEN ERROR MalformedPattern ELSE rightBracketSeen ← TRUE;
	patternString[psIndex] ← rightBracketPattern};
    ENDCASE =>
      BEGIN
      patternString[psIndex] ← char;
      IF psIndex = 0 THEN {firstPatternCharIsNormal ← TRUE; firstPatChar1 ← char};
      END;
  psIndex ← psIndex + 1;
  ENDLOOP;
FOR psIndex ← psIndex, psIndex - 1 UNTIL psIndex = 0 DO -- discard trailing "any's"
  SELECT patternString[psIndex - 1] FROM
    anyStringPattern, anyAlphaPattern, anyNonAlphaPattern => NULL;
    ENDCASE => EXIT;
  ENDLOOP;
IF (patternString.length ← psIndex) = 0 THEN ERROR MalformedPattern;
IF stackSize > 0 THEN {
  textPosStack ← NEW[TextStackArray[stackSize]];
  patternPosStack ← NEW[PatternStackArray[stackSize]] };
END; -- of OPEN
END; -- of Create


Try: PUBLIC PROC [finder: Finder, rope: Rope, start: Int ← 0, len: Int ← MaxLen]
  RETURNS [found: BOOLEAN, at, atEnd, patternEnd: Int] = 
BEGIN OPEN finder;

stackPtr, patternPos, patternAnchor: NAT ← 0;
char, patternChar: Char ← 377C;
charType: CharProperty;
beginPos, endPos, textPos, textAnchor, end, size: Int;

size ← ropeI.Size[rope];
start ← MIN[start,size];
len ← MIN[len,size-start];
end ← start+len;
found ← FALSE;
at ← start;
UNTIL at = end DO -- text loop
  IF firstPatternCharIsNormal THEN -- search for next instance of first pattern char
    BEGIN
    rdrI.SetPosition[reader,rope,at];
    UNTIL at = end DO
      SELECT rdrI.Get[reader] FROM
        firstPatChar1, firstPatChar2 => EXIT;
        ENDCASE;
      at ← at+1;
      ENDLOOP;
    IF at=end THEN EXIT;
    patternPos ← 1;
    textPos ← at + 1;
    END
  ELSE  {patternPos ← 0; textPos ← at};
  -- save where name began matching non-* seg of pattern
  patternAnchor ← 0;
  beginPos ← textAnchor ← at;
  DO -- pattern loop
    IF patternPos >= patternString.length THEN
      BEGIN
      found ← TRUE;
      at ← beginPos;
      atEnd ← IF rightBracketSeen THEN endPos ELSE textPos;
      patternEnd ← textPos;
      GO TO Return;
      END;
    SELECT (patternChar ← patternString[patternPos]) FROM
      anyStringPattern =>
        {patternAnchor ← patternPos ← patternPos + 1; textAnchor ← textPos; stackPtr ← 0};
      leftBracketPattern =>
        {beginPos ← textPos; patternPos ← patternPos +1};
      rightBracketPattern =>
        {endPos ← textPos; patternPos ← patternPos +1};
      anyNonAlphaPattern, anyAlphaPattern =>
        BEGIN
        stackPtr ← stackPtr + 1;
        textPosStack[stackPtr] ← textPos;
        patternPosStack[stackPtr] ← patternPos;
        patternPos ← patternPos + 1;
        END;
      ENDCASE =>
        BEGIN
        IF textPos >= end THEN GO TO Return; -- failure return
        IF patternChar = oneCharPattern THEN
          BEGIN
	  rdrI.SetPosition[reader,rope,textPos];
          [] ← rdrI.Get[reader];
          IF patternPos # 0 AND patternPos = patternAnchor THEN
            -- first char(s) of * segment
           {patternAnchor ← patternAnchor + 1; textAnchor ← textPos + 1};
          patternPos ← patternPos + 1;
          textPos ← textPos + 1;
          END
        ELSE BEGIN
	  rdrI.SetPosition[reader,rope,textPos];
          charType ← GetCharProp[char ← rdrI.Get[reader]];
	  IF SELECT patternChar FROM
	  	oneNonAlphaPattern => charType # alphaNumeric,
		oneAlphaPattern => charType = alphaNumeric,
		char => TRUE,
		> LineBreakValue =>
		    SELECT patternChar ← Inline.BITAND[CharMask,patternChar] FROM
		       IN ['A..'Z] => patternChar = UpperCase[char],
		       IN ['a .. 'z] => patternChar = LowerCase[char],
		       ENDCASE => FALSE,
		ENDCASE => FALSE
            THEN -- chars match -- {patternPos ← patternPos + 1; textPos ← textPos + 1}
          ELSE BEGIN -- chars don't match; try to increment some wild card position
            WHILE stackPtr # 0 DO
	      rdrI.SetPosition[reader,rope,textPosStack[stackPtr]];
              charType ← GetCharProp[rdrI.Get[reader]];
	      IF SELECT patternString[patternPosStack[stackPtr]] FROM
	        anyNonAlphaPattern => charType # alphaNumeric,
		anyAlphaPattern => charType = alphaNumeric,
		ENDCASE => FALSE THEN
                BEGIN
                patternPos ← patternPosStack[stackPtr] + 1;
                textPos ← textPosStack[stackPtr] ← textPosStack[stackPtr] + 1;
                EXIT
                END;
              stackPtr ← stackPtr - 1;
              ENDLOOP;
            IF stackPtr = 0 THEN -- implicit AND patternAnchor # 0 (there was a *)
              BEGIN
              patternPos ← patternAnchor;
              textPos ← textAnchor ← textAnchor + 1;
              END;
            IF patternAnchor = 0 AND stackPtr = 0 THEN EXIT;
            END;
          END;
        END;
    ENDLOOP; -- end of pattern loop
  at ← at+1; -- start over with next character
  ENDLOOP; -- end of text loop
GO TO Return;
EXITS
Return => NULL;
END; -- of Try --

charPropertyTable: PUBLIC REF READONLY CharPropertyTable;

StartRopeFind: PUBLIC PROC = {
  cpt: REF CharPropertyTable ← NEW[CharPropertyTable];
  ch: Char;
  i: CARDINAL;
  punctuationString: STRING = "!@#$%~&*()-`=+[{]};:'"",.<>/?\|←↑"L;
  charPropertyTable ← cpt;
  FOR ch IN Char DO cpt[ch] ← illegal; ENDLOOP;
  FOR ch IN LegalCharacters DO cpt[ch] ← alphaNumeric; ENDLOOP;
  cpt[TAB] ← white; cpt[SP] ← white; cpt[CR] ← white;
  cpt[140C] ← punctuation;
  FOR i IN [0 .. punctuationString.length) DO cpt[punctuationString[i]] ← punctuation; ENDLOOP;
  };
  
END.