4
\$\begingroup\$

Exercise 1-22. Write a program to "fold" long input lines into two or more shorter lines after the last non-blank character that occurs before the n-th column of input. Make sure your program does something intelligent with very long lines, and if there are no blanks or tabs before the specified column.

The key idea is that I process whitespace character by character while processing a sequence of non-blank characters block by block, i.e. I read a block then decide whether to print it in the current line or next line.

#include <stdio.h> 

#define LINE_MAX_SIZE 21        /* The maximum number of columns allowed in a single line */
#define IN_NON_BLANK_SEQ 1      /* Indicate that a sequence of non-blank character are being processed */ 
#define TAP_STOP_DIST 5

#define NON_BLANK(ch)   (ch != '\t' && ch != '\n' && ch != ' ') 

int get_next_tap_pos(int current_column) {
    // columns numbered 0, 1, 2... there is a tap stop at 0, 5, 10, 15, 20
    return current_column / TAP_STOP_DIST * TAP_STOP_DIST + TAP_STOP_DIST; 
}

int main(void) 
{
    char block[LINE_MAX_SIZE+1];
    int column_pos = 0;                 /* Indicate the current column in the output where the next character will be printed*/
    int index_non_blank = 0;            /* Indicate the number of the non-blank char in the next non-blank char sequence */ 
    int state = !IN_NON_BLANK_SEQ;      /* Indicate whether a non-blank character is being processed */ 

    int ch; 
    while ( (ch = getchar()) != EOF) {
        if (NON_BLANK(ch)) {

            state = IN_NON_BLANK_SEQ; 
            if (index_non_blank == LINE_MAX_SIZE) {
                // There is a word bigger than the size of the line
                block[LINE_MAX_SIZE] = '\0'; 
                // Should I print it in the current line or in the next line ?
                // Our word is as big as our line. so if current line is not empty, we can't print it here
                if (column_pos != 0) 
                    putchar('\n');
                printf("%s\n", block); 
                column_pos = 0; 
                index_non_blank = 0;
                block[index_non_blank] = ch;  
            }
            else {
                block[index_non_blank] = ch; 
                ++index_non_blank; 
            }
        }
        else {
            // Is there a non-blank sequence that just finished ?
            if (state == IN_NON_BLANK_SEQ) {
                // Should the current word be printed in the current line or next line ?
                block[index_non_blank] = '\0'; 
                int expected_column_num = column_pos + index_non_blank; 
                if (expected_column_num <= LINE_MAX_SIZE) {
                    printf("%s", block); 
                    column_pos = expected_column_num; 
                }
                else {
                    putchar('\n'); 
                    printf("%s", block); 
                    column_pos = index_non_blank; 
                }

                state = !IN_NON_BLANK_SEQ;  
                index_non_blank = 0; 
            }
            // now print the whitespace character and update column position 
            if (ch == '\n') {
                putchar(ch); 
                column_pos = 0; 
            }
            else if (ch == ' ' && column_pos < LINE_MAX_SIZE) {
                putchar(ch); 
                ++column_pos; 
            }
            else if (ch == '\t' && column_pos < LINE_MAX_SIZE) {
                putchar(ch); 
                column_pos = get_next_tap_pos(column_pos); 
            }
            else {
                putchar('\n');
                column_pos = 0;  
            }
        }
    }
}

Github link

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

This looks pretty competent code to me. Good to see that you've included handling for tabs.

I only have minor points to raise:

  • #define NON_BLANK(ch)   (ch != '\t' && ch != '\n' && ch != ' ') 
    

    If we include <ctype.h>, we should be able to use isspace() instead of this macro. In any case, a function is preferred to a macro, especially a macro that expands its arguments more than once (we can't call this with an argument such as *++p, for example).

  • #define IN_NON_BLANK_SEQ 1
    

    We only have two states, so we should use a boolean (with a better name than state). Assuming we have <stdbool.h>, then I'd replace int state = !IN_NON_BLANK_SEQ; with

       bool in_blank_seq = false;
    

    Actually, we might not need this at all - I think that when we have most recently seen whitespace, then index_non_blank is non-zero.

  • int column_pos = 0;
    int index_non_blank = 0;
    

    These two could be unsigned. But that might not matter, given the small range of values they ever hold.

  • #define TAP_STOP_DIST 5
    int get_next_tap_pos(int current_column) {
    

    Spelling - that should be tab. And what kind of terminal has tab stops every five characters? Pretty much every terminal (and terminal emulator) I've ever used has tab stops every eight characters, at least unless specifically changed.

  • #define LINE_MAX_SIZE 21
    

    Additional exercise - make this configurable, by command-line option.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ I agree that using isspace() is preferable. There are several other escape sequences that will produce whitespace, such as '\v' (vertical tab), '\r', '\b', etc. \$\endgroup\$
    – Andy Alt
    Commented Jan 23, 2022 at 6:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.