# Write a Program Detab That Replaces Tabs in the Input with the Proper Number of Blanks to Space to the Next Tab Stop

Continuing my K&R journey!

Please see below for my solution to:

Exercise 1-20: Write a program detab that replaces tabs in the input with the proper number of blanks to space to the next tab stop. Assume a fixed set of tab stops, say every n columns.

#include <stdio.h>

#define TAB         '\t'        //  character to look for
#define SPACE       '-'         //  tab replacement char
#define TABSIZE     8           //  fixed set of tab stops
#define MAXLINE     8000        //  maximum number of chars in a line

static int getLine(char line[]);

static void processLine(char line[], int lineCharCount);

int main() {
int len;                //length of line
char line[BUFSIZ];      //string constant array for appending ch

while ((len = getLine(line)) > 0) {
processLine(line, len);
}
return 0;
}

static int getLine(char line[]) {
int c;
int i;

for (i = 0; i < MAXLINE - 1 && (c = getchar()) != EOF && c != '\n'; ++i) {
line[i] = c;
}
if (c == '\n') {
line[i] = c;
++i;
}
line[i] = '\0';
return i;
}

static void processLine(char line[], int lineCharCount) {
int nonTabCount;            // running count of characters up until \t is encountered
int i;                      // for loop
nonTabCount = 0;

for (i = 0; i < lineCharCount; i++) {
if (line[i] == TAB) {
nonTabCount++;
line[i] = SPACE;
putchar(line[i]);
while (TABSIZE - nonTabCount > 0) {
putchar(SPACE);
nonTabCount++;
}
nonTabCount = 0;
} else {
putchar(line[i]);
nonTabCount++;
if (nonTabCount >= TABSIZE) {
nonTabCount = 0;
}
}
}
}

• How is a hyphen the same as a space? Jan 18 at 2:03
• Oh dear, I forgot to change back to space..... having hyphens was helpful while building the program. Gosh darnit. Jan 18 at 2:18

Overall: Aside from bug, a very good post.

Nice layout & formatting

Good object names

Bug: Inconsistent size

With char line[BUFSIZ]; and C definition of "The value of the macro BUFSIZ shall be at least 256.", calling getLine(line) is a problem. getLine() assumes the buffer is MAXLINE or 8000.

Better to use the same. Even better, pass the size into getLine().

// static int getLine(char line[]) {
static size_t getLine(char line[], size_t size) {


I like size first to allow static code analysis with:

static size_t getLine(size_t size, char line[size]) {


In this case, best to initialize to handle tiny sizes.

// int c;
int c = 0;


Pedantic: string size

String size can exceed INT_MAX. Better to use size_t to express lineCharCount to handle all strings.

// processLine(char line[], int lineCharCount)
processLine(char line[], size_t lineCharCount)


Then use size_t i; in the function.

Simplify

Simpler and handles unsigned math well - no overflow.

// while (TABSIZE - nonTabCount > 0) {
while (TABSIZE > nonTabCount) {


Printing

I'd print the processed line afterwards and avoid printing in processLine(). Of course that is tricky as the line grows.

Why change line[]?

processLine() has line[i] = SPACE;, but why change only 1 space? If anything the de-tabify process might add multiple spaces and that complicates things. Code instead could putchar(SPACE); and leave line[] unchanged.

If processLine() changed to leave char line[] unchanged, consider making that paramter const char line[].

String?

With processLine(char line[], int lineCharCount), I'd expect the function received a pointier to a string, in which case, the lineCharCount is not needed. Instead iterate until a null character is found.

Yet by passing lineCharCount does allow handling of a line of input with a null character that was read - an advance functionality.

Fixed tab size

"Assume a fixed set of tab stops, say every n columns." --> that sounds like n should be an option at program start to de-tabify at different values. Maybe that is for the next iteration of code.

Documentation

Coding goal deserves to be in the code as a comment.

When getLine() fills the buffer, yet the line is still not done, consider how one would re-work the function to convey that incomplete read to the caller and if the remainder of the line should remain unread.

Declare and initialize in one step

// int nonTabCount;
// nonTabCount = 0;

int nonTabCount = 0;

• No review on #define SPACE '-' as OP already id'ed that typo. Jan 18 at 3:51
• iamericfletcher, Post lacks tests to show that code works. Next time, consider a test harness. Jan 18 at 4:10
• I'm confused - in getLine in main(), what would be the size I'm passing into it? Would I declare size_t size; and then use that like so while ((len = getLine(size, line)) > 0)? Jan 18 at 11:37
• Also, would I keep char line[BUFSIZ]; the way it is in main()? Jan 18 at 11:40
• @iamericfletcher More like in main(), char line[BUFSIZ]; ... while ((len = getLine(sizeof line, line)) > 0). Jan 18 at 14:33