PROGRAM removecc;

{ Program to read a disk file   }
{ and remove any control characters or change them to }
{ a sequence of printable characters.   }
{ Also finds any high-bit-set characters,  }
{ strips the high bit and optionally prefixes the char }
{ with an escape character. }

{Assume the escape char for control chars is chosen to  }
{be "%".  Then this table shows how control codes are   }
{represented in the output file.  }

{  binary char value (ORD)    representation
  -------------------------   --------------
    0  NUL  %@
    1  SOH  %A
    2  STX  %B
    .        .
    .        .
    .        .
   25  EM   %Y
   26  SUB  %Z
   27  ESC  %[
   28  FS   %\
   29  GS   %]
   30  RS   %^
   31  US   %_
   92  %    %%       ***** note this special representation!
  127  DEL  %?       ***** note this special representation!
 }



CONST
   version = '1.1';
   sector_size = 128;   {#bytes in a sector}

   carriage_return = 13; {^M}
   line_feed  = 10;      {^J}
   eof_char  = 26;       {^Z}

TYPE
   byte = 0..255;
   sector_array = PACKED ARRAY [1..sector_size] OF byte;
   sector_file  = FILE OF sector_array;

   outch_array = PACKED ARRAY [1..3] OF byte;
   char12 = PACKED ARRAY [1..12] OF CHAR;
VAR
   infile   :sector_file;
   infilename   :char12;

   outfile   :sector_file;
   outfilename   :char12;

   list_flag   :BOOLEAN;    {list output}


   s_recno     :INTEGER;
   in_buffer   :sector_array;
   in_bufptr   :INTEGER;
 
   out_buffer   :sector_array;
   out_bufptr   :INTEGER;

   ctr_highbit   :INTEGER;
   ctr_cc        :INTEGER;

   esc_highbit_char   :byte;  {escape char for highbit chars}
   esc_control_char   :byte;  {escape char for control chars}

   status   :INTEGER;

{----------------------------------------------------------}
{----------------------------------------------------------}
 
PROCEDURE ask_escape_chars;

VAR
   flag   :BOOLEAN;
   response   :CHAR;

BEGIN
   flag := TRUE;
   WHILE flag DO BEGIN
      WRITE ('Enter the control-chars escape character: ');
      READLN (response);
      IF response=' ' THEN BEGIN
         WRITELN ('No escape char; control codes remain as is.');
         esc_control_char := 0;
         flag := FALSE;
         END
      ELSE IF response IN ['!', '#', '$', '%', '&', '*',
                           '|', '~', '`', '''', '{', '}',
                           '=', '"', '<', '>', '/']
         THEN BEGIN
             flag := FALSE;
             esc_control_char := ORD (response);
         END
      ELSE BEGIN
            WRITELN('*** Not an acceptable character. Try again.');
      END{IF};
   END{WHILE};

   flag := TRUE;
   WHILE flag DO BEGIN
      WRITE ('Enter the highbit-chars escape character: ');
      READLN (response);
      IF response=' ' THEN BEGIN
         WRITELN ('No escape char; high bits will be stripped.');
         esc_highbit_char := 0;
         flag := FALSE;
         END
      ELSE IF response IN ['!', '#', '$', '%', '&', '*',
                           '|', '~', '`', '''', '{', '}',
                           '=', '"', '<', '>', '/']  THEN BEGIN
          flag := FALSE;
          esc_highbit_char := ORD (response);
          END
      ELSE BEGIN
            WRITELN('*** Not an acceptable character. Try again.');
      END{IF};
      IF (esc_highbit_char>0) AND
            (esc_control_char = esc_highbit_char) THEN BEGIN
         WRITELN ('*** Cannot be the same as the control escape char.');
         WRITELN ('    Try again. ');
         flag := TRUE;
      END{IF};
   END{WHILE};
END{PROCEDURE};


{--------------------------------------------------}

{ Translates the char in in_char into a 1 to 3 byte}
{ sequence stored in out_chars.  Sets nchars to the}
{ # of chars. }

PROCEDURE xlate_char ( in_char :byte;
                   VAR out_chars :outch_array;
                   VAR nchars :INTEGER );

BEGIN{PROCEDURE}
   nchars := 0;
   IF in_char > 127 THEN BEGIN
      {Handle high-bit chars}
      in_char := in_char - 128;
      ctr_highbit := ctr_highbit + 1;
      IF esc_highbit_char > 0  THEN BEGIN
         nchars := nchars + 1;
         out_chars[nchars] := esc_highbit_char;
      END{IF};
   END{IF};

   IF (in_char>31) AND (in_char<127) THEN BEGIN
      {Handle "ordinary" characters. }
      nchars := nchars + 1;
      out_chars[nchars] := in_char;
      IF (in_char=esc_control_char) OR 
         (in_char=esc_highbit_char)
      THEN BEGIN
         nchars := nchars + 1;
         out_chars[nchars] := in_char;
      END{IF};
     END
   ELSE IF (in_char=carriage_return) OR
           (in_char=line_feed) THEN BEGIN
         nchars := nchars + 1;
         out_chars[nchars] := in_char; 
      END
   ELSE IF (in_char<=31) OR (in_char=127) THEN BEGIN
      { Handle control chars. }
      { We have already excluded CR and LF}
      ctr_cc := ctr_cc + 1;
      IF esc_control_char=0 THEN BEGIN
         nchars := nchars + 1;
         out_chars[nchars] := in_char;
         END
      ELSE BEGIN
         nchars := nchars + 1;
         out_chars[nchars] := esc_control_char;
         nchars := nchars + 1;
         out_chars[nchars] := in_char + ORD('@');
         IF in_char=127  THEN out_chars[nchars] := ORD('?');
      END{IF};
   END{IF};
END{PROCEDURE};


{-------------------------------------------------------------}

FUNCTION open_infile  :INTEGER;

BEGIN{FUNCTION}
   WRITE('Enter the input filename: ');
   infilename := '            ';
   READLN(infilename);

   RESET(infilename,infile);

   in_bufptr := sector_size + 1;

   open_infile := 0;
   IF EOF(infile) THEN open_infile := -1;

END{FUNCTION};

{-------------------------------------------------------------}

FUNCTION open_outfile   :INTEGER;

BEGIN{FUNCTION}
   WRITE('Enter the output filename: ');
   outfilename := '            ';
   READLN (outfilename);

   REWRITE (outfilename,outfile);

   out_bufptr := 0;

   open_outfile := 0;
END{FUNCTION};

{--------------------------------------------------------}
{Reads the next sector from the input file. }
{Returns 0 = normal;  -1 = error or EOF. }

FUNCTION read_infile  :INTEGER;

BEGIN{FUNCTION}
   IF EOF(infile) THEN BEGIN
      read_infile := -1;
      in_bufptr := sector_size + 1;
      END
   ELSE BEGIN
      READ (infile, in_buffer);
      in_bufptr := 0;
      read_infile := 0;
   END{IF};
END{FUNCTION};

{--------------------------------------------------------}
{Writes the next sector into the output file. }
{Returns 0 = normal,  <0 if error. }

FUNCTION write_outfile    :INTEGER;

BEGIN{FUNCTION}
   WRITE(outfile, out_buffer);
   out_bufptr := 0;
   write_outfile := 0;
END{FUNCTION};
  

{--------------------------------------------------------}

FUNCTION close_infile  :INTEGER;

BEGIN{FUNCTION}
   close_infile := 0;
END{FUNCTION};


{--------------------------------------------------------}

FUNCTION close_outfile  :INTEGER;
BEGIN{FUNCTION}
   close_outfile := 0;
END{FUNCTION};


{--------------------------------------------------------}
{Gets the next char (pseudochar, a byte) from the input buffer.}
{Signals EOF by returning -1.  Returns 0 if get a char. }


FUNCTION get_char ( VAR in_char :byte )  :INTEGER; 

VAR
   status   :INTEGER;

BEGIN{FUNCTION}
   status := 0;
   IF in_bufptr >= sector_size THEN BEGIN
      status := read_infile;
   END{IF};

   IF status = 0 THEN BEGIN
      in_bufptr := in_bufptr + 1;
      in_char := in_buffer[in_bufptr];
      IF in_char = eof_char THEN status := -1;
   END{IF};

   get_char := status;
END{FUNCTION};

{--------------------------------------------------------}

FUNCTION put_char (out_char :byte)  :INTEGER;

VAR
   status   :INTEGER;

BEGIN
   status := 0;

   out_bufptr := out_bufptr + 1;
   out_buffer[out_bufptr] := out_char;
   
   IF out_bufptr >= sector_size THEN BEGIN
      status := write_outfile;
   END{IF};

   put_char := status;
END{FUNCTION};


{--------------------------------------------------------}
{Purge the last buffer load to the output file.}

PROCEDURE put_purge;

VAR
   i       :INTEGER;
   remaining   :INTEGER;
   status   :INTEGER;

BEGIN{PROCEDURE}
   remaining := sector_size - out_bufptr;
   FOR i:= 1 TO remaining DO BEGIN
      status := put_char (eof_char);
   END{FOR};
END{PROCEDURE};


{--------------------------------------------------------}

PROCEDURE pause;
 
VAR
   response   :CHAR; 

BEGIN{PROCEDURE}
   WRITELN('enter CR to continue');
   READLN(response);
END{PROCEDURE};

{--------------------------------------------------}
FUNCTION copy_file  :INTEGER;

VAR
   status   :INTEGER;
   i        :INTEGER;
   in_char  :byte;
   out_chars   :outch_array;
   nchars   :INTEGER;

BEGIN{FUNCTION}
   status := 0;
   WHILE status = 0  DO BEGIN
      status := get_char (in_char);
      IF status <> 0 THEN BEGIN
         put_purge;
        END
      ELSE BEGIN
         xlate_char (in_char, out_chars, nchars);
         FOR i := 1 TO nchars DO BEGIN
            IF status = 0 THEN status := put_char (out_chars[i]);
         END{FOR};
      END{IF};
   END{WHILE};
   copy_file := status;
END{FUNCTION};


{--------------------------------------------------}
{--------------------------------------------------}

BEGIN{PROGRAM}
   WRITELN ('RemoveCC  Version ',version);

   ctr_cc := 0;
   ctr_highbit := 0;

   status := open_infile; 
   IF status <> 0 THEN BEGIN
      WRITELN('Could not open file ', infilename);
   END{IF};

   IF status = 0 THEN BEGIN
      status := open_outfile;
      IF status <> 0 THEN BEGIN
         WRITELN('Could not open output file ',outfilename);
      END{IF};
   END{IF};

   IF status=0 THEN BEGIN
      ask_escape_chars;
   END{IF};


   IF status = 0 THEN BEGIN
      status := copy_file;
   END{IF};

   WRITELN(ctr_cc, ' control chars.  ',
           ctr_highbit, ' high-bit chars.');

   status := close_input;
   status := close_output;

END{PROGRAM}.

