//  REMTAB.CPP
    //  Replaces tab characters with spaces in text files.
    //  Author: Peter Meyer

    #include <fstream.h>        //  This automatically includes <iostream.h>
    #include <stdlib.h>
    #include <string.h>
    #include <direct.h>
    
    #define TRUE  1
    #define FALSE 0
    #define loop while(TRUE)
    
    #define TAB       9
    #define LINEFEED 10
    #define SPACE    32
    
    #define MAX_TAB_SIZE       8
    #define MIN_TAB_SIZE       1
    #define DEFAULT_TAB_SIZE   4
    
    #define MAX_LINE_LENGTH 1023
    
    int tab_size, num_tabs=0;
    
    char *version = "1.1";
    char line[MAX_LINE_LENGTH+1];
    char tab_spaces[MAX_TAB_SIZE+1];
    
    ifstream input_file;
    ofstream output_file;
    
    void read_command_line(int ac, char *argv[]);
    void set_tab(void);
    void open_files(char *argv[]);
    int read_line(char *argv[]);
    void write_line(char *argv[]);
    void replace_tabs(void);
    void terminate(char *argv[]);
    
    /*-----------------------------*/
    void main(int argc, char *argv[])
    {
    cout << "\nREMTAB.EXE, Version " << version << "++, Copyright 1999 Hermetic Systems";
    cout << "\nThis program replaces tab characters with spaces in a text file.";
    
    _getcwd(line,sizeof(line));
    cout << "\nCurrent directory is " << line << endl;
    
    read_command_line(argc,argv);
    set_tab();
    open_files(argv);
    
    loop
        {
        if ( !read_line(argv) )
            terminate(argv);
        replace_tabs();
        write_line(argv);
        }
    }
    
    /*----------------------------------------*/
    void read_command_line(int ac, char *argv[])
    {
    if ( ac < 3 || ac > 4 )
        {
        cout << "\nUse: REMTAB input_file output_file";
        cout << "\n or: REMTAB input_file output_file tab_size";
        cout << "\ntab_size defaults to 4 spaces." << endl;
        exit(0);
        }
    
    //  open() does not check for inconsistent openings of same file.
    if ( !strcmp(argv[1],argv[2]) )
        {
        cout << "\nOutput file must be different from input file." << endl;
        exit(1);
        }
    
    tab_size = ( ac > 3 ? atoi(argv[3]) : DEFAULT_TAB_SIZE );
    }
    
    /*--------------*/
    void set_tab(void)
    {
    int i;
    
    if ( tab_size < MIN_TAB_SIZE || tab_size > MAX_TAB_SIZE )
        {
        cout << "\nInvalid value " << tab_size << " for tab_size "
             << "(must be in the range " << MIN_TAB_SIZE << " through "
             << MAX_TAB_SIZE << "." << endl;
        exit(2);
        }
    
    i = tab_size;
    while ( --i >= 0 )
        tab_spaces[i] = SPACE;
    
    tab_spaces[tab_size] = 0;
    }
    
    /*-------------------------*/
    void open_files(char *argv[])
    {
    input_file.open(argv[1]);
    if ( !input_file )
        {
        cout << "\nCannot open input file " << argv[1] << endl;
        exit(3);
        }
    
    output_file.open(argv[2]);
    if ( !output_file )
        {
        cout << "\nCannot open output file " << argv[2] << endl;
        input_file.close();
        exit(4);
        }
    
    cout << "\nInput file " << argv[1] << " opened for reading." << endl;
    }
    
    //  This differs somewhat from the C version.
    /*-----------------------*/
    int read_line(char *argv[])
    {
    input_file.getline(line,sizeof(line),LINEFEED);
    //  This reads a maximum of sizeof(line)-1 bytes
    //  and throws away the LINEFEED (if read).
    
    return ( input_file ? TRUE : FALSE );
    }
    
    /*-------------------------*/
    void write_line(char *argv[])
    {
    output_file << line << endl;
    
    if ( !output_file )
        {
        cout << "\nError when writing to output file " << argv[2] << "." << endl;
        exit(6);
        }
    }
    
    //  This replaces all occurrences of tab characters in line[]
    //  with the appropriate number of spaces
    //  (as long as there is sufficient space in line[]).
    /*-------------------*/
    void replace_tabs(void)
    {
    int i, num_spaces;
    char *ptr1, *ptr2;
    
    while ( ( ptr1 = strchr(line,TAB) ) != NULL )
        {
        num_tabs++;
        num_spaces = tab_size - (ptr1-line)%tab_size;
    
        if ( strlen(line) + num_spaces > sizeof(line) - 1 )
            {
            cout << "\nExpanded line exceeds maximum length of " << sizeof(line)-1
                 << " characters." << endl;
            exit(7);
            }
    
        ptr2 = strchr(line,0) + 1;
    
        while ( --ptr2 > ptr1 )
            *(ptr2+num_spaces-1) = *ptr2;
    
        for ( i=0; i<num_spaces; i++ )
            *(ptr1+i) = SPACE;
        }
    }
    
    /*------------------------*/
    void terminate(char *argv[])
    {
    input_file.close();
    output_file.close();
    
    cout << "\nOutput file " << argv[2] << " written. ";
    
    if ( !num_tabs )
        cout << "No tab characters found." << endl;
    else
        cout << num_tabs << " tab character" << (num_tabs>1?"s":"")
        <<  " replaced." << endl;
    
    exit(0);
    }