(*------------------------------------------------------------------------*) IMPLEMENTATION MODULE DoSearch; (* $T-, $R- trusted *) (*------------------------------------------------------------------------*) FROM SYSTEM IMPORT ADR, WORD; FROM CharCodes IMPORT NewLineCh, CapitalCh; FROM Strings IMPORT LengthC; FROM List IMPORT True, False, ListP; FROM Actions IMPORT BuiltInActionWithParams; IMPORT Display, DisplayPosition, Errors, Interface; IMPORT Windows, Buffers, StringData, Memory, List, Fast; IMPORT Execute, Movements; IMPORT Actions; (* Read-a-key junk *) FROM Memory IMPORT ArrayByteP; FROM SearchBase IMPORT FieldText, PatternMeta, ReplaceMeta, FieldType, PatternType, ReplaceType, FieldSet, PatternSet, Case, Coerce, Direction, PatternPtr, ReplacePtr, NewPattern, DisposePattern, NewReplace, DisposeReplace, Traverse; FROM ReadSearch IMPORT ReadPattern, ReadReplace; (* :* IMPORT Debug; FROM ReadSearch IMPORT ShowPType, ShowRType, ShowPattern, ShowReplace; *: *) (*------------------------------------------------------------------------*) (* To save parameter passing costs, all procedures take a single VAR record parameter containing all they require (& more) *) TYPE Args = RECORD window: Windows.WindowP; data: Memory.ArrayByteP; area: Buffers.AreaR; start: CARDINAL; (* updated by search *) pattern: PatternPtr; replace: ReplacePtr; forwards: BOOLEAN; anchored: BOOLEAN; traverse: BOOLEAN; END; VAR correction: INTEGER; (*------------------------------------------------------------------------*) PROCEDURE Search (VAR args: Args): BOOLEAN; VAR last: CARDINAL; step: INTEGER; optimize: BOOLEAN; res: INTEGER; PROCEDURE Find (): BOOLEAN; VAR fLast: CARDINAL; fStep: INTEGER; c: CHAR; p, i: CARDINAL; BEGIN fLast := last; fStep := step; WITH args DO WITH pattern^.seqList^ DO c := str^[0]; CASE sCase OF Sensitive: LOOP IF data^[start] = c THEN p := start + 1; i := 1; LOOP IF i >= len THEN RETURN TRUE END; IF data^[p] # str^[i] THEN EXIT END; INC (p); INC (i) END (* loop *); ELSIF start = fLast THEN RETURN FALSE END (* if *); INC (start, fStep) END (* loop *); | Insensitive: LOOP IF UpperCase[CARDINAL(data^[start])] = c THEN p := start + 1; i := 1; LOOP IF i >= len THEN RETURN TRUE END; IF UpperCase[CARDINAL(data^[p])] # str^[i] THEN EXIT END; INC (p); INC (i) END (* loop *); ELSIF start = fLast THEN RETURN FALSE END (* if *); INC (start, fStep); END (* loop *); END (* case *); END (* with *); END (* with *); END Find; (*---------------------------------------------------------------------*) PROCEDURE Match (matchPatt: PatternPtr; position: CARDINAL): BOOLEAN; VAR i, b, pos: CARDINAL; c: CHAR; s: BITSET; pat: PatternPtr; BEGIN (* :* Debug.WriteS ("Match ("); ShowPType (matchPatt^.type); Debug.WriteS (", "); Debug.WriteC (position); Debug.WriteS (")*N"); Debug.WriteS ("Match: pattern details are:*N"); ShowPattern (matchPatt, 3); *: *) WITH args DO WITH matchPatt^ DO match.Start := position; CASE type OF CharP: IF position >= area.End THEN RETURN FALSE END; match.End := match.Start + 1; IF cCase = Sensitive THEN IF (ch # data^[position]) THEN RETURN FALSE END; ELSE IF (ch # UpperCase[CARDINAL(data^[position])]) THEN RETURN FALSE END; END (* if *); | CharSetP: IF position >= area.End THEN RETURN FALSE END; match.End := match.Start + 1; c := data^ [position]; b := ORD (c) MOD 32; (* register allocation fail *) s := set[ORD (c) DIV 32]; (* register allocation fail *) IF NOT (b IN s) THEN RETURN FALSE END; | CharStringP: match.End := match.Start + len; IF match.End > area.End THEN RETURN FALSE END; IF sCase = Sensitive THEN i := 0; REPEAT IF str^[i] # data^[position] THEN RETURN FALSE END; INC (i); INC (position) UNTIL i = len; ELSE i := 0; REPEAT IF str^[i] # UpperCase[CARDINAL(data^[position])] THEN RETURN FALSE END; INC (i); INC (position) UNTIL i = len; END (* if *); | ExclusionP: (* Assume for now that ReadPattern will only allow exclusion of patterns that will match exactly (any) one charcter *) IF position >= area.End THEN RETURN FALSE END; match.End := match.Start + 1; RETURN NOT Match (patt, position); (* | FieldP: fieldArea := field^.match; match.End := match.Start + (fieldArea.End - fieldArea.Start); IF match.End >= area.End THEN RETURN FALSE END; IF fCase = Sensitive THEN i := fieldArea.Start; REPEAT c := data^ [i]; CASE coerce OF Same: (* nothing *) | Upper: c := UpperCase [CARDINAL (c)] | Lower: IF ('A' <= c) AND (c <= 'Z') THEN c := VAL (CHAR, ORD (c) + 020H); END; END (* case *); IF c # data^[position] THEN RETURN FALSE END; INC (i); INC (position) UNTIL i = fieldArea.End; ELSE i := fieldArea.Start; REPEAT IF UpperCase[CARDINAL(data^[i])] # UpperCase[CARDINAL(data^[position])] THEN RETURN FALSE END; INC (i); INC (position); UNTIL i = fieldArea.End; END (* if *); *) | MinimumP: (* A bit of a bugger, this one. Current algorithm is to bump match count while able and while matchPatt^.next sequence fails to match. This is very inefficient, and moreover incorrect for (currently trapped) patterns with a *-match in an alternate part (where we should match the sequence following the AlternateP - not the next element of it !!) *) match.End := match.Start; LOOP pos := match.End; pat := matchPatt^.next; LOOP IF pat = NIL THEN RETURN TRUE END; IF Match (pat, pos) THEN pos := pat^.match.End; pat := pat^.next; ELSIF Match (minPatt, match.End) THEN match.End := minPatt^.match.End; EXIT ELSE RETURN FALSE END (* if *); END (* loop *); END (* loop *); | MaximumP: (* In fact current Acorn definition is all (>= 1) *) IF Match (maxPatt, position) THEN REPEAT match.End := maxPatt^.match.End UNTIL NOT Match (maxPatt, match.End); ELSE RETURN FALSE END (* if *); | AlternateP: pat := altList; LOOP IF pat = NIL THEN RETURN FALSE END; IF Match (pat, position) THEN match.End := pat^.match.End; EXIT END (* if *); pat := pat^.next; END (* loop *); | SequenceP: match.End := match.Start; pat := seqList; WHILE pat # NIL DO IF Match (pat, match.End) THEN match.End := pat^.match.End ELSE RETURN FALSE END (* if *); pat := pat^.next END (* while *); END (* case *); END (* with *); END (* with *); RETURN TRUE END Match; (*---------------------------------------------------------------------*) BEGIN (* :* Debug.WriteS ("Search: args are:*N"); ShowArgs (args, FALSE); *: *) WITH args DO IF forwards THEN last := area.End; step := + 1; IF start > last THEN RETURN FALSE END; ELSE last := area.Start; step := -1; IF start < last THEN RETURN FALSE END; END (* if *); optimize := (NOT anchored) AND (pattern^.seqList^.type = CharStringP); LOOP IF Interface.EscapeFlag THEN Interface.EscapeFlag := FALSE; RETURN FALSE END (* if *); IF optimize THEN IF Find () THEN (* whoopee do *) ELSE RETURN FALSE END (* if *); END (* if *); IF Match (pattern, start) THEN EXIT END; IF anchored OR (start = last) THEN RETURN FALSE END; INC (start, step) END (* loop *); WITH window^ DO IF traverse THEN Movements.Jump (Buffer, pattern^.match.End); IF forwards THEN correction := -INTEGER(Buffer^.After.Start - Buffer^.Before.End); res := Traverse (pattern, CorrectPatt, 0); END (* if *); ELSE Movements.Jump (Buffer, pattern^.match.Start); IF NOT forwards THEN correction := +INTEGER(Buffer^.After.Start - Buffer^.Before.End); res := Traverse (pattern, CorrectPatt, 0); END (* if *); END (* if *); END (* with *); END (* with *); RETURN TRUE END Search; (*------------------------------------------------------------------------*) PROCEDURE CorrectPatt (pattern: PatternPtr; args: WORD): INTEGER; BEGIN WITH pattern^ DO INC (match.Start, correction); INC (match.End, correction); END (* with *); RETURN 0 END CorrectPatt; (*------------------------------------------------------------------------*) PROCEDURE Replace (VAR args: Args): BOOLEAN; (* Replaces matched pattern at after.start by replace at before.end *) (*---------------------------------------------------------------------*) PROCEDURE AddNone (buffer: Buffers.BufferP; data: ArrayByteP; size: CARDINAL); VAR i: CARDINAL; BEGIN WITH buffer^ DO i := 0; WHILE i < size DO Array.Data^[Before.End] := data^[i]; INC (i); INC (Before.End) END (* while *); END (* with *); END AddNone; (*---------------------------------------------------------------------*) PROCEDURE AddUpper (buffer: Buffers.BufferP; data: ArrayByteP; size: CARDINAL); VAR i: CARDINAL; BEGIN WITH buffer^ DO i := 0; WHILE i < size DO Array.Data^[Before.End] := UpperCase[CARDINAL(data^[i])]; INC (i); INC (Before.End) END (* while *); END (* with *); END AddUpper; (*---------------------------------------------------------------------*) PROCEDURE AddLower (buffer: Buffers.BufferP; data: ArrayByteP; size: CARDINAL); VAR i: CARDINAL; ch: CHAR; BEGIN WITH buffer^ DO i := 0; WHILE i < size DO ch := data^[i]; IF ('A' <= ch) AND (ch <= 'Z') THEN ch := CHR (ORD (ch) + 020H); END; Array.Data^[Before.End] := ch; INC (i); INC (Before.End); END (* while *); END (* with *); END AddLower; (*---------------------------------------------------------------------*) VAR r: ReplacePtr; size: CARDINAL; BEGIN (* :* Debug.WriteS ("Replace: args are:*N"); ShowArgs (args, TRUE); *: *) WITH args.window^ DO IF NOT (Buffers.ChangesAllowedF IN Buffer^.Status) THEN Errors.Report (INTEGER (Errors.ThisBufferCannotBeChanged)); RETURN FALSE END (* if *); size := 0; r := args.replace; WHILE r # NIL DO WITH r^ DO CASE type OF CharStringR: INC (size, len) | FieldR: INC (size, field^.match.End - field^.match.Start) END (* case *); END (* with *); r := r^.next END (* while *); IF Buffer^.Before.End + size > Buffer^.After.Start THEN Errors.Report (INTEGER(Errors.NoRoomInBuffer)); RETURN FALSE END (* if *); r := args.replace; WHILE r # NIL DO WITH r^ DO CASE type OF CharStringR: AddNone (Buffer, ADR (str^[0]), len); | FieldR: size := field^.match.End - field^.match.Start; CASE r^.coerce OF None: AddNone (Buffer, ADR (args.data^[field^.match.Start]), size); | Upper: AddUpper (Buffer, ADR (args.data^[field^.match.Start]), size); | Lower: AddLower (Buffer, ADR (args.data^[field^.match.Start]), size); END (* case *); END (* case *); END (* with *); r := r^.next END (* while *); Buffer^.After.Start := args.pattern^.match.End; INCL (Buffer^.Status, Buffers.ModifiedF); RETURN TRUE END (* with *); END Replace; (*------------------------------------------------------------------------*) PROCEDURE MakeArea (VAR args: Args); VAR marker: Buffers.MarkerP; truncated: BOOLEAN; BEGIN WITH args DO WITH window^ DO IF forwards THEN area := Buffer^.After; start := area.Start; marker := NIL; truncated := FALSE; WHILE Buffers.MarkerWithin (Buffer, area, marker) AND NOT truncated DO IF marker^.DisplaySize > 0 THEN area.End := marker^.Where; truncated := TRUE END (* if *); END (* while *); ELSE (* backwards *) area := Buffer^.Before; start := area.End (* - 1 *); marker := NIL; WHILE Buffers.MarkerWithin (Buffer, area, marker) DO IF marker^.DisplaySize > 0 THEN area.Start := marker^.Where + 1 END (* if *); END (* while *); END (* if *); END (* with *); END (* with *); END MakeArea; (*------------------------------------------------------------------------*) PROCEDURE UpdateArea (VAR args: Args; afterReplace: BOOLEAN); (* Update search area & start position after an interactive (incl. All) search and possibly replace. Assert: args.traverse = FALSE *) VAR len: CARDINAL; BEGIN WITH args DO IF forwards THEN IF afterReplace THEN area.Start := pattern^.match.End; start := area.Start ELSE area.Start := pattern^.match.Start; start := start + 1 END (* if *); ELSE IF afterReplace THEN area.End := start; start := area.End ELSE start := start - 1; (* area.End is unaltered, but must restore data delimited by it to Before.End *) len := area.End - pattern^.match.Start; WITH window^.Buffer^ DO Fast.Move (Array, len, After.Start, Before.End); INC (Before.End, len); INC (After.Start, len); END (* with *); END (* if *); END (* if *); END (* with *); END UpdateArea; (*------------------------------------------------------------------------*) PROCEDURE DoSearch (window: Windows.WindowP; searchStr: ARRAY OF CHAR; forwards: BOOLEAN): BOOLEAN; VAR args: Args; BEGIN IF NOT ReadPattern (searchStr, args.pattern) THEN RETURN FALSE END (* if *); args.window := window; args.data := window^.Buffer^.Array.Data; args.forwards := forwards; args.anchored := FALSE; args.traverse := FALSE; MakeArea (args); (* Since (this) interactive search doesn't traverse match, it is desirable to start match at cursor +/- 1 to cause previous_find to move after a match *) IF forwards THEN INC (args.start) ELSE DEC (args.start) END; (* :* Debug.WriteS ("DoSearch.DoSearch: pattern is:-*N"); ShowPattern (args.pattern, 3); *: *) IF Search (args) THEN DisposePattern (args.pattern); DisplayPosition.SetInvalidColumn (window); RETURN TRUE END (* if *); DisposePattern (args.pattern); Errors.Warning (INTEGER(Errors.SearchNotFound)); RETURN FALSE END DoSearch; (*------------------------------------------------------------------------*) PROCEDURE DisposePatternAndReplace (VAR args: Args); BEGIN DisposePattern (args.pattern); DisposeReplace (args.replace); END DisposePatternAndReplace; (*-----------------------------------------------------------------------*) PROCEDURE DoReplace (window: Windows.WindowP; searchStr: ARRAY OF CHAR; replaceStr: ARRAY OF CHAR; forwards: BOOLEAN): BOOLEAN; CONST EscapeKey = 0C4H; (* Characters actually, but > 127, so no can do, *) HelpKey = 0C5H; (* even if willing to use octal. *) (*---------------------------------------------------------------------*) PROCEDURE AddChar (ch: CHAR); BEGIN WITH Windows.Selected^.Buffer^ DO IF Before.End < After.Start THEN Array.Data^[Before.End] := ch; INC (Before.End) END (* if *); END (* with *); END AddChar; (*---------------------------------------------------------------------*) PROCEDURE AddText (text: ARRAY OF CHAR); VAR index, length: CARDINAL; BEGIN length := LengthC (text); index := 0; WHILE index < length DO AddChar (text [index]); INC (index); END (* while *); END AddText; (*---------------------------------------------------------------------*) PROCEDURE Acknowledge (text: ARRAY OF CHAR); BEGIN AddText (text); Display.Something END Acknowledge; (*--------------------------------------------------------------------*) (*---------------------------------------------------------------------*) VAR args: Args; promptEnd: CARDINAL; key: CHAR; result: BOOLEAN; junk: Actions.ActionR; BEGIN IF NOT ReadPattern (searchStr, args.pattern) THEN RETURN FALSE END; IF NOT ReadReplace (args.pattern, replaceStr, args.replace) THEN DisposePattern (args.pattern); RETURN FALSE END (* if *); args.window := window; args.data := window^.Buffer^.Array.Data; args.forwards := forwards; args.anchored := FALSE; args.traverse := FALSE; MakeArea (args); AddChar (NewLineCh); AddText ("R(eplace), S(kip), O(nce), A(ll), E(scape) or H(elp) ? "); promptEnd := Windows.Selected^.Buffer^.Before.End; WHILE Search (args) DO DisplayPosition.SetInvalidColumn (window); WITH Windows.Selected^ DO Buffer^.Before.End := promptEnd; Cursor.Position.Y := Edge.Bottom; END (* with *); DisplayPosition.SetInvalidColumn (Windows.Selected); Display.Something; LOOP WHILE Execute.Idle () DO Display.Something END; key := CapitalCh (Execute.NextAction (junk)); IF key = CHAR(EscapeKey) THEN key := 'E' ELSIF key = CHAR(HelpKey) THEN key := 'H' END (* if *); CASE key OF 'R': Acknowledge ("Replace"); IF Replace (args) THEN UpdateArea (args, TRUE); ELSE DisposePatternAndReplace (args); RETURN FALSE END (* if *); EXIT | 'S': Acknowledge ("Skip"); UpdateArea (args, FALSE); EXIT | 'O': result := Replace (args); DisposePatternAndReplace (args); RETURN result | 'E' (*, EscapeCh *): DisposePatternAndReplace (args); RETURN TRUE | 'A': Acknowledge ("All"); REPEAT IF Replace (args) THEN UpdateArea (args, TRUE); ELSE DisposePatternAndReplace (args); RETURN FALSE END (* if *); UNTIL NOT Search (args); DisposePatternAndReplace (args); RETURN TRUE | 'H' (*, HelpCh *): (* not implemented *) ELSE Errors.Warning (INTEGER(Errors.ReplacePleaseAnswerWithRSOAEH)) END (* case *); END (* loop *); END (* while *); DisplayPosition.SetInvalidColumn (window); DisposePatternAndReplace (args); Errors.Warning (INTEGER(Errors.SearchNotFound)); RETURN FALSE END DoReplace; (*------------------------------------------------------------------------*) PROCEDURE ProgrammedSearch (params: ListP): ListP; (* (Search searchS forwardsB anchoredB traverseB) *) VAR activeWindow: Windows.WindowP; searchStr: ARRAY [0..99] OF CHAR; args: Args; BEGIN activeWindow := Windows.Selected; IF NOT (StringData.CopyDC (List.StringOfList (List.Head (params)), searchStr) AND ReadPattern (searchStr, args.pattern)) THEN RETURN False END (* if *); args.window := activeWindow; args.data := activeWindow^.Buffer^.Array.Data; args.forwards := List.BooleanOfList (List.Head (params^.Tail)); args.anchored := List.BooleanOfList (List.Head (params^.Tail^.Tail)); args.traverse := List.BooleanOfList (List.Head (params^.Tail^.Tail^.Tail)); MakeArea (args); IF Search (args) THEN DisposePattern (args.pattern); DisplayPosition.SetInvalidColumn (activeWindow); RETURN True END (* if *); DisposePattern (args.pattern); Errors.Warning (INTEGER (Errors.SearchNotFound)); RETURN False END ProgrammedSearch; (*------------------------------------------------------------------------*) PROCEDURE ProgrammedReplace (params: ListP): ListP; (* (Replace searchS replaceS forwardsB anchoredB traverseB) *) VAR activeWindow: Windows.WindowP; searchStr: ARRAY [0..99] OF CHAR; replaceStr: ARRAY [0..99] OF CHAR; args: Args; traverse: BOOLEAN; replacePos: CARDINAL; BEGIN activeWindow := Windows.Selected; IF NOT (StringData.CopyDC (List.StringOfList (List.Head (params)), searchStr) AND ReadPattern (searchStr, args.pattern) AND StringData.CopyDC (List.StringOfList (List.HeadOfTail (params)), replaceStr) AND ReadReplace (args.pattern, replaceStr, args.replace)) THEN RETURN False END (* if *); args.window := activeWindow; args.data := activeWindow^.Buffer^.Array.Data; args.forwards := List.BooleanOfList (List.Head (params^.Tail^.Tail)); args.anchored := List.BooleanOfList (List.Head (params^.Tail^.Tail^.Tail)); args.traverse := FALSE; traverse := List.BooleanOfList (List.Head (params^.Tail^.Tail^.Tail^.Tail)); MakeArea (args); IF Search (args) THEN DisplayPosition.SetInvalidColumn (activeWindow); replacePos := activeWindow^.Buffer^.Before.End; IF Replace (args) THEN IF NOT traverse THEN Movements.Jump (activeWindow^.Buffer, replacePos) END (* if *); DisposePatternAndReplace (args); RETURN True ELSE DisposePatternAndReplace (args); RETURN False END (* if *) END (* if *); DisposePatternAndReplace (args); Errors.Warning (INTEGER(Errors.SearchNotFound)); RETURN False; END ProgrammedReplace; (*------------------------------------------------------------------------*) PROCEDURE Initialise; BEGIN BuiltInActionWithParams ("Search", ProgrammedSearch, "Programmed search", "searchS forwardsB anchoredB traverseB"); BuiltInActionWithParams ("Replace", ProgrammedReplace, "Programmed replace", "searchS replaceS forwardsB anchoredB traverseB"); END Initialise; (*------------------------------------------------------------------------*) PROCEDURE Terminate; BEGIN END Terminate; (*------------------------------------------------------------------------*) (* :* PROCEDURE ShowArgs (args: Args; includeReplace: BOOLEAN); VAR i: CARDINAL; ch: CHAR; BEGIN WITH args DO Debug.WriteS ("area.Start = "); Debug.WriteC (area.Start); Debug.WriteS (" ["); i := area.Start; WHILE (i <= (area.Start + 10)) AND (i < area.End) DO ch := data^ [i]; IF (' ' <= ch) AND (ch <= '~') THEN Debug.Wrch (ch) ELSE Debug.Wrch ('.') END (* if *); INC (i); END (* while *); Debug.Writeln; Debug.WriteS ("area.End = "); Debug.WriteC (area.End); Debug.Wrch (' '); IF (area.End - area.Start) < 10 THEN i := area.Start ELSE i := area.End - 10 END (* if *); WHILE i < area.End DO ch := data^ [i]; IF (' ' <= ch) AND (ch <= '~') THEN Debug.Wrch (ch) ELSE Debug.Wrch ('.') END (* if *); INC (i); END (* while *); Debug.WriteS ("]*N"); Debug.WriteS ("start = "); Debug.WriteC (start); Debug.Writeln; Debug.WriteS ("pattern is:*N"); ShowPattern (pattern, 3); IF includeReplace THEN Debug.WriteS ("replace is:*N"); ShowReplace (replace, 3); END (* if *); Debug.WriteS ("forwards = "); Debug.WriteB (forwards); Debug.Writeln; Debug.WriteS ("anchored = "); Debug.WriteB (anchored); Debug.Writeln; Debug.WriteS ("traverse = "); Debug.WriteB (traverse); Debug.Writeln; END (* with *); END ShowArgs; (*------------------------------------------------------------------------*) *: *) VAR UpperCase: ARRAY [0..255] OF CHAR; c: CARDINAL; BEGIN FOR c := 0 TO 255 DO UpperCase[c] := CapitalCh (CHAR(c)) END (* for *); END DoSearch. (*------------------------------------------------------------------------*)