K&R C Programs Exercise 5-12

Exercise 5-12. Extend entab and detab to accept the shorthand entab -m +n to mean tab stops every n columns, starting at column m. Choose convenient (for the user) default behavior.

Two new argument forms extend the programs from Exercise 5-11: -m sets the starting column and +n sets the interval. So detab -4 +3 means stops at columns 4, 7, 10, 13, … Default if omitted: start at column 8, interval 8 (same as before). Plain numeric arguments (e.g., detab 4 8 16) continue to work as explicit stop positions.

Solution — Argument Parsing

/* K&R Exercise 5-12 — detab/entab with -m +n shorthand
 * Compile: gcc -ansi -Wall detab12.c -o detab12
 * Usage:   ./detab12 -4 +3   OR  ./detab12 4 8 12  OR  ./detab12 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXSTOPS 200
#define DEFAULT_START    8
#define DEFAULT_INTERVAL 8

static int tabstops[MAXSTOPS];
static int ntabs = 0;

static void build_stops(int start, int interval)
{
    int col;
    ntabs = 0;
    for (col = start; col < MAXSTOPS && ntabs < MAXSTOPS; col += interval)
        tabstops[ntabs++] = col;
}

static int next_tabstop(int col)
{
    int i;
    for (i = 0; i < ntabs; i++)
        if (tabstops[i] > col)
            return tabstops[i];
    /* past all defined stops: revert to interval-8 */
    return col - (col % DEFAULT_INTERVAL) + DEFAULT_INTERVAL;
}

static void parse_args(int argc, char *argv[])
{
    int start = -1, interval = -1, i;
    int explicit[MAXSTOPS];
    int nexplicit = 0;

    for (i = 1; i < argc; i++) {
        if (argv[i][0] == '-' && argv[i][1] != '\0')
            start = atoi(argv[i] + 1);
        else if (argv[i][0] == '+')
            interval = atoi(argv[i] + 1);
        else if (nexplicit < MAXSTOPS)
            explicit[nexplicit++] = atoi(argv[i]);
    }

    if (nexplicit > 0) {
        /* explicit stops take precedence */
        for (i = 0; i < nexplicit; i++)
            tabstops[ntabs++] = explicit[i];
    } else {
        build_stops(start  < 0 ? DEFAULT_START    : start,
                    interval < 0 ? DEFAULT_INTERVAL : interval);
    }
}

int main(int argc, char *argv[])
{
    int c, col = 0, spaces, next;

    parse_args(argc, argv);

    while ((c = getchar()) != EOF) {
        if (c == '\t') {
            next = next_tabstop(col);
            for (spaces = next - col; spaces > 0; spaces--)
                putchar(' ');
            col = next;
        } else if (c == '\n') {
            putchar(c); col = 0;
        } else {
            putchar(c); col++;
        }
    }
    return 0;
}

Test It

printf "\thello\tworld\n" | ./detab12           # every 8 (default)
printf "\thello\tworld\n" | ./detab12 -4 +4    # stops at 4,8,12,16...
printf "\thello\tworld\n" | ./detab12 4 8 16   # explicit stops

What This Exercise Teaches

  • Multi-form argument parsing — a single argv scan distinguishes three forms by the leading character (-, +, digit); this is the classic Unix option-parsing pattern before getopt
  • Generating stop tables at runtime — when stops follow an arithmetic pattern (-m +n), precompute the full table rather than computing on every character; faster inner loop
  • Default selectionstart < 0 ? DEFAULT_START : start uses the sentinel value -1 to mean “not specified”; clean and avoids a separate flag variable

Set Up Your C Environment

← Exercise 5-11  | 
Chapter 5 Solutions  | 
Exercise 5-13 →

Book: The C Programming Language, 2nd Ed — Kernighan & Ritchie

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>