Exercise 1-21

Write a program entab that replaces strings of blanks by the minimum number of tabs and blanks to achieve the same spacing. Use the same tab stops as for detab. When either a tab or a single blank would suffice to reach a tab stop, which should be given preference?

We first define the symbolic constant TABWIDTH to represent how many columns are between tab stops, and col which keeps track of which column we are on by incrementing after a character is read in and printed, and resetting to zero whenever a newline is read in.

    #include <stdio.h>
    
    #define TABWIDTH 4  /* columns between tab stops */
    
    /* entab:  replace strings of spaces with tabs */
    main()
    {
        int c;
        int col;    /* current column */
    
        col = 0;
        while ((c = getchar()) != EOF) {
            putchar(c);
            ++col;
            if (c == '\n')
                col = 0;
        }
        return 0;
    }

Whenever we come across a blank, we enter a while-loop that continues to run as long as the subsequent character is also a blank. During every iteration, we increment a variable called blanks that keeps track of how many consecutive blanks we come across. If a tab character is entered, or col + blanks reaches the next tab stop and blanks is greater than one (otherwise single blanks could get converted to tabs), we print a tab character and reset blanks to zero. We also make sure to print a tab character if a single blank that reached a tab stop and was buffered is superseded by more blanks (e.g abc  d; notice how the first blank reaches a tab stop [four in this case] but there is more whitespace afterward implying this is an indent.) Once we exit the loop, we print any leftover blanks and continue reading in more input.

    #include <stdio.h>
    
    #define TABWIDTH 4  /* columns between tab stops */
    
    /* entab:  replace strings of spaces with tabs */
    main()
    {
        int c;
        int col;    /* current column */
        int blanks; /* number of consecutive blanks */
    
        col = blanks = 0;
        while ((c = getchar()) != EOF) {
            while (c == ' ' || c == '\t') {
                /* account for previously skipped single blanks */
                if ((col + blanks) % TABWIDTH == 0 && blanks == 1) {
                    putchar('\t');
                    ++col;
                    blanks = 0;
                }
                ++blanks;
                /* do not print tab character in place of single blank */
                if (((col + blanks) % TABWIDTH == 0 && blanks > 1) || c == '\t') {
                    putchar('\t');
                    col = col + (TABWIDTH - col % TABWIDTH);
                    blanks = 0;
                }
                c = getchar();
            }
            while (blanks > 0) {
                putchar(' ');
                --blanks;
            }
            putchar(c);
            ++col;
            if (c == '\n')
                col = 0;
        }
        return 0;
    }

Note: the expression TABWIDTH - col % TABWIDTH is equal to the number of characters needed to reach the tab column because col % TABWIDTH is equal to the number of characters after the last tab stop.

Note: it is a good idea to not convert single blanks that lie on a tab stop because otherwise, regular blanks could get converted to tabs.