-- FFind.mesa last edited by:
-- JGS		 1-Jul-82 12:42:59
-- Sweet	17-Sep-82 13:58:28

DIRECTORY
  Ascii,
  ByteBlt,
  Environment,
  Exec,
  FileTransfer,
  Format,
  Heap,
  MSegment,
  Profile,
  Stream;

FFind: PROGRAM IMPORTS ByteBlt, Exec, FileTransfer, Format, Heap, MSegment, Profile, Stream =

  BEGIN

  maxPatternLength: CARDINAL = 100;
  pagesPerBuffer: CARDINAL = 100;
  charsPerBuffer: CARDINAL = pagesPerBuffer*Environment.charsPerWord;
  conn: FileTransfer.Connection ← NIL;
  chars: LONG POINTER TO PACKED ARRAY OF CHARACTER;
  out: Format.StringProc;
  exec: Exec.Handle;
  pattern: LONG STRING ← NIL;
  Delta1: TYPE = ARRAY CHARACTER OF INTEGER;
  Delta2: TYPE = RECORD [SEQUENCE COMPUTED INTEGER OF INTEGER];
  delta1: LONG POINTER TO Delta1 ← NIL;
  delta2: LONG POINTER TO Delta2 ← NIL;
  nMatches, nFiles: LONG CARDINAL ← 0;
  ignoreCase: BOOLEAN;
  
  ExecCalling: Exec.ExecProc =
    BEGIN
    name, switches: LONG STRING ← NIL;
    bufferSegment: MSegment.Handle ← NIL;
    Finalize: PROC = {
      IF delta1 # NIL THEN Heap.systemZone.FREE[@delta1];
      IF delta2 # NIL THEN Heap.systemZone.FREE[@delta2];
      IF bufferSegment # NIL THEN {
        MSegment.Delete[bufferSegment];  bufferSegment ← NIL; chars ← NIL};
      IF conn # NIL THEN {
        FileTransfer.Close[conn];  FileTransfer.Destroy[conn];  conn ← NIL};
      IF name # NIL THEN name ← Exec.FreeTokenString[name]};
    BEGIN
        ENABLE {
      ABORTED => GO TO aborted; 
      FileTransfer.Error --[code]-- => 
        SELECT code FROM
	  login   => {LoginUser[clientData: NIL];  RETRY};
	  retry   => GOTO timedOut;
	  unknown => GOTO fileTransferProblem;
	  ENDCASE;
      UNWIND => Finalize[]};
    exec ← h;
    out ← Exec.OutputProc[h];
    bufferSegment ← MSegment.Create[pages: pagesPerBuffer, release: []];
    chars ← MSegment.Address[bufferSegment];
    nMatches ← nFiles ← 0;
    conn ← FileTransfer.Create[];
    FileTransfer.SetProcs[
      conn: conn, clientData: NIL, messages: PutMessages, login: LoginUser];
    FileTransfer.SetPrimaryCredentials[
      conn: conn, user: Profile.userName, password: Profile.userPassword];
    [pattern, switches] ← Exec.GetToken[h];
    -- do any switch processing here
    ignoreCase ← FALSE;
    IF switches # NIL THEN {
      effect: BOOLEAN ← TRUE;
      FOR i: CARDINAL IN [0..switches.length) DO
        SELECT switches[i] FROM
          '~, '-  => effect ← FALSE;
          'c,'C => {ignoreCase ← effect; effect ← TRUE};
	  ENDCASE  => effect ← TRUE;
        ENDLOOP;
      switches ← Exec.FreeTokenString[switches]};
    IF pattern = NIL THEN GOTO noPattern;
    IF ignoreCase THEN
      FOR i: CARDINAL IN [0..pattern.length) DO
        pattern[i] ← ToLower[pattern[i]];
	ENDLOOP;
    MakeFailureFunctions[pattern];
    DO
      IF Exec.CheckForAbort[h] THEN {outcome ← abort; EXIT};
      [name, switches] ← Exec.GetToken[h];
      switches ← Exec.FreeTokenString[switches];
      IF name = NIL THEN EXIT;
      SearchFile[name];
      name ← Exec.FreeTokenString[name];
      IF Exec.CheckForAbort[h] THEN {outcome ← abort; EXIT};
      ENDLOOP;
    Format.LongDecimal[out, nFiles];
    Format.Line[out, " files searched"L];
    SELECT nMatches FROM
      0 => Format.Text[out, "No"L];
      ENDCASE => Format.LongDecimal[out, nMatches];
    Format.Text[out, " match"L];
    IF nMatches # 1 THEN {Format.Char[out, 'e]; Format.Char[out, 's]};
    Format.Text[out, " found"];
    outcome ← normal;
    Finalize[];
    EXITS
      noPattern => {
        outcome ← error;
        Format.CR[out];  Format.Line[out, "...no pattern specified"L]};  
      aborted => {
        outcome ← abort;
        Format.CR[out];  Format.Line[out, "...aborted"L]};  
      timedOut => {
        outcome ← error;
        Format.CR[out];  Format.Line[out, "...connection timed out!"L]};  
      fileTransferProblem => {
        outcome ← error;
        Format.CR[out];
	Format.Line[out, "...unknown FileTransfer problem!"L]};  
    END; -- of ENABLE
    END;
    
  PutMessages: FileTransfer.MessageProc = {
    IF level = fatal THEN { 
      Format.Text[out, "Fatal error: "L];
      IF s1 # NIL THEN Format.Text[out, s1];
      IF s2 # NIL THEN Format.Text[out, s2];
      IF s3 # NIL THEN Format.Text[out, s3];
      IF s4 # NIL THEN Format.Text[out, s4]}};
    
  LoginUser: FileTransfer.ClientProc --[clientData: LONG POINTER]-- = {
    user: STRING = [40];
    password: STRING = [40];
    Exec.GetNameandPassword[exec, user, password];
    FileTransfer.SetPrimaryCredentials[
      conn: conn, user: user, password: password]};
      
  SearchFile: PROC [name: LONG STRING] = {
    OPEN FileTransfer;
    ENABLE Error => IF code = skip THEN CONTINUE;
    vfn: VFN ← AllocVFN[name];
    stream: Stream.Handle;
    stream ← ReadStream[conn, vfn ! UNWIND => FreeVFN[vfn]];
    WHILE stream # NIL DO
      ENABLE UNWIND => FreeVFN[vfn];
      stream.options.signalEndOfStream ← TRUE;
      nFiles ← nFiles + 1;
      SearchStream[stream];
      stream ← ReadNextStream[stream ! Error => IF code = skip THEN CONTINUE]
      ENDLOOP};
    
  SearchStream: PROC [stream: Stream.Handle] = {
    source: FileTransfer.FileInfo;
    j, k: INTEGER;
    m: INTEGER = pattern.length;
    nChars: INTEGER;
    eof: BOOLEAN ← FALSE;
    bufferOffset: LONG CARDINAL ← 0;
    firstChar: CHARACTER = pattern[0];
    
    LoadBlock: PROCEDURE = INLINE
      BEGIN
      bufferOffset ← bufferOffset + charsPerBuffer - maxPatternLength;
      k ← maxPatternLength + k - nChars;
      [] ← ByteBlt.ByteBlt[
        from: [LOOPHOLE[chars], charsPerBuffer - maxPatternLength, charsPerBuffer],
        to: [LOOPHOLE[chars], 0, charsPerBuffer]];
      nChars ← Stream.GetBlock[
        stream, [LOOPHOLE[chars], maxPatternLength, charsPerBuffer] !
        Stream.EndOfStream => {eof ← TRUE; CONTINUE}].bytesTransferred + maxPatternLength;
      IF ~eof THEN nChars ← charsPerBuffer;
      END;
      
    ShowMatch: PROC [index: INTEGER] =
      BEGIN
      begin, end: INTEGER ← index;
      THROUGH [0..100) WHILE end < nChars DO
        IF chars[end] = Ascii.CR THEN EXIT;
        end ← end + 1;
        ENDLOOP;
      THROUGH [0..100) WHILE begin > 0 DO
        IF chars[begin-1] = Ascii.CR THEN EXIT;
        begin ← begin - 1;
        ENDLOOP;
      Format.LongDecimal[out, bufferOffset+index];
      Format.Text[out, ": "L];
      Format.Block[out, [LOOPHOLE[chars], begin, end]];
      Format.CR[out];
      nMatches ← nMatches + 1;
      END;
            
    IF Exec.CheckForAbort[exec] THEN ERROR ABORTED;
    source ← FileTransfer.GetStreamInfo[stream];
    Format.Text[out, "*** "L];
    Format.Line[out, source.body];
    nChars ← Stream.GetBlock[stream, [LOOPHOLE[chars], 0, charsPerBuffer] !
      Stream.EndOfStream => {eof ← TRUE; CONTINUE}].bytesTransferred;
    IF ~eof THEN nChars ← charsPerBuffer;
    k ← m;
    DO
      DO
        IF k <= nChars THEN EXIT;
	IF eof THEN RETURN; 
	LoadBlock[];
	ENDLOOP;
      j ← m;
      WHILE j > 0 AND chars[k-1] = pattern[j-1] DO
        j ← j-1; k ← k-1;
	ENDLOOP;
      IF j = 0 THEN {ShowMatch[k]; k ← k+m+1}
      ELSE k ← k + MAX[delta1[chars[k-1]], delta2[j-1]];
      ENDLOOP;
      };
      

  MakeFailureFunctions: PROCEDURE [pat: LONG STRING] =
    BEGIN
    -- See Knuth, Morris, Pratt,  "Fast Pattern ...", SIAM J. Comp, June 1977
    i, j, k, t: INTEGER;
    m: INTEGER = pat.length;
    f: LONG POINTER TO Delta2 ← 
      Heap.systemZone.NEW[Delta2[m+1]]; -- auxilliary array
    
    delta1 ← Heap.systemZone.NEW[Delta1 ← ALL[m]];
    FOR i IN [1..m] DO delta1[pat[i-1]] ← m-i; ENDLOOP;
    
    delta2 ← Heap.systemZone.NEW[Delta2[m]];
    FOR k IN [1..m] DO
      delta2[k-1] ← 2*m - k;
      ENDLOOP;
    j ← m; t ← j + 1;
    WHILE j > 0 DO
      f[j] ← t;
      WHILE t <= m AND pat[j-1] # pat[t-1] DO
        delta2[t-1] ← MIN [delta2[t-1], m-j];
	t ← f[t];
	ENDLOOP;
      t ← t-1; j ← j-1;
      ENDLOOP;
    FOR k IN [1..t] DO
      delta2[k-1] ← MIN[delta2[k-1], m+t-k];
      ENDLOOP;
    Heap.systemZone.FREE[@f];
    END;

  ToLower: PROCEDURE [ch: CHARACTER] RETURNS [CHARACTER] = INLINE {
  -- test order significant
    IF ch <= 'Z AND ch >= 'A THEN ch ← ch + ('a - 'A); RETURN[ch]};

  Exec.AddCommand["FFind.~"L, ExecCalling];

  END.