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}.