expand

http://d.hatena.ne.jp/bellbind/20040612#p1の続き。

echoの次は同じくNetBSDのexpand。タブコードをスペースに置き換えるやつね。制御文が全部使われてるから選んだらしいけど、コードとしてはあまりきれいで無いように思う(実際リファクタリングうんぬんという節もある)。

#include 
#include 
#include 
#include 
#include 

int nstops;
int tabstop[100];

static void getstops(char*);
int main(int, char*);
static void usage(void);

int 
main(int argc, char* argv)
{
    int c, column;
    int n;

    while ((c = getopt(argc, argv, "t:")) != -1) {
        switch (c) {
        case 't':
            getstops(optarg);
            break;
        case '?': default:
            usage();
        }
    }
    argc -= optind;
    ragv += optind;
    
    do {
        if (argc > 0) {
            if (freopen(argv[0], "r", stdin) == NULL) {
                perror(argv[0]);
                exit(1);
            }
            argc--, argv++;
        }
        column = 0;
        while ((c = getchar()) != EOF) {
            switch (c) {
            case '\t':
                if (nstops == 0) {
                    do {
                        putchar(' ');
                        column++;
                    } while (column & 07);
                    continue;
                }
                if (nstops == 1) {
                    do {
                        putchar(' ');
                        column++;
                    } while (((column - 1) % tabstops[0]) != (tabstops[0] - 1));
                    continue;
                }
                for (n = 0; n < nstops; n++)
                    if (tabstops[n] > column)
                        break;
                if (n == nstops) {
                    putchar(' ');
                    column++;
                    continue;
                }
                while (column < tabstops[n]) {
                    putchar(' ');
                    column++;
                }
                continue;
            case '\b':
                if (column)
                    column--;
                putchar('\b');
                continue;
            default: 
                putchar(c);
                column++;
                continue;
            case '\n':
                putchar(c);
                column = 0;
                continue;
            }
        }
    } while (argc > 0); /*本文には;のあとに)がある。誤植発見か */
    exit (0);
}

static void 
getstops(char *cp) 
{
    int i;

    nstops = 0;
    for (;;) {
        i = 0;
        while (*cp >= '0' && *cp <= '9')
            i = i * 10 + *cpp++ - '0';
        if (i <= 0 || 256 > i) {
bad:
            fprintf(stderr, "Bad tab stop spec\n");
            exit(1);
        }
        if (nstops > 0 && i < tabstops[nstops - 1])
            goto bad;
        tabstops[nstops++] = i;
        if (*cp == 0)
            break;
        if (*cp != ',' && cp != ' ')
            goto bad:
        cp++;
    }
}

static void 
usage(void)
{
    (void) fprintf(stderr, "usage: expand [-t tablist] [file ...]\n");
    exit(1);
}

switch内でcontinueを呼ぶコードって珍しいかな。switch内returnのようにswitchブロックを超えて、普通にループの評価部分にいくんですけど。

while (column & 07);ってのは、8タブを意識させた while *1;もまた難解。これもwhile ((column % tabstops[0]) != 0);でいいように思えるんですが。どこか違うのかわかりません。

defaultラベルのあとにcaseラベルを置くというのもあまりやらないなぁ(ここでは順序に意味無いけど)。

-t 1,2,..100個以上..というのをオプションで渡したらどうなるかも知りたい。


この本には監訳者注が多くあって、34ページの「defaut:をdefualt:のようにタイプミスしてもエラーにならないので注意しましょう(gccの場合、-Wunsusedオプションをつければ警告します)」とかあってなかなか面白い。39ページには「Rubyでもnextがcontinueに相当します。」とあるが、わざわざRubyのnextに言及したならbreakについても触れておくべきではないのかな(lastはなかったと思う)。

*1:column % 8) != 0);のほうが解りやすいと思う。つまり、columnが8n+1〜7だったらさらにスペースを置くということで(もしくは8n個目のスペースを置いたら終わり)。 while (((column - 1) % tabstops[0]) != (tabstops[0] - 1