第44回
仕様設計からコーディングまで~タブ/スペース変換プログラムを作る(4)

各関数の動作

mainから呼び出される各関数の動作を説明していきましょう。

putusage~使用方法を表示する(リスト5)

このプログラムの使用方法を表示します。これまではサンプルという意味もあって、エラー表示はシンプルなものにしていました。今回はいかにもユーティリティーらしく、オプションの指定方法などを丁寧に説明することにします。

結果がリダイレクトされることを踏まえ、文字列はfputs関数で標準エラー出力に送ります。

使用方法を表示する場合、コマンドライン・パラメータで“-h”を指定するという仕様もあります。しかしこのプログラムでは、単純に
stconv
とだけ入力すれば使用方法を表示する――という形にしています。

このあたりの仕様変更は、コマンドラインを解析する処理で対応できます。

リスト5:使用方法を表示するputusage関数
void    putusage(void)
{
  fputs("STCONV : タブ/スペース変換フィルタ¥n",
         stdout);
  fputs("¥x7¥tSTCONV option <input file> <option> <input file> ...¥n", stdout);
  fputs("¥t¥t---- <option> の説明 ----¥n", stdout);
  fputs("¥t-Tn   : スペースを「タブコード」に変換します。¥n", stdout);
  fputs("¥t        1バイトだけのスペースはタブに変換されません。¥n", stdout);
  fputs("¥t-Sn   : タブコードを「スペース」に変換します。¥n", stdout);
  fputs("¥tオプション文字に続く n でタブストップの間隔を指定します。¥n", stdout);
  fputs("¥t<input file> で入力ファイルを指定します。¥n", stdout);
  fputs("¥t入力ファイルに<option>を指定しない場合は、それ以前に指定された¥n", stdout);
  fputs("¥tオプションを引き継ぎます。¥n", stdout);
  fputs("¥t処理結果の出力は標準出力になっているので、リダイレクトしてください。¥n¥n", stdout);
}

convinit~構造体のメンバを初期化(リスト6)

入力ファイルの情報を保持するSTCONV型構造体の各メンバを初期化します。

まだ実際のデータが読み込まれていない状態なので、変換方向を示すswには0、タブストップ幅を示すtabには規定値のDEFAULT_TAB、ファイル名を示すfnameとファイルポインタを示すfpにはNULLを代入しておきます。

実際には、コマンドラインから入力ファイルの情報を読み込む段階でメンバに正しい値が代入されるので、わざわざ初期化しなくても大丈夫です。ただ、今回のプログラムでは構造体を配列にしたため、プログラムの開始時に既に構造体が生成されています。そこで、念のために初期化を行っておきます。

リスト6:構造体のメンバを初期化するconvinit関数
void    convinit(STCONV *p)
{
  p->sw = 0;
  p->tab = DEFAULT_TAB;
  p->fname = NULL;
  p->fp = NULL;
}

chk_opt : オプションを解析して構造体にデータをセット(リスト7)

コマンドラインオプションから、入力ファイル名、変換方向などを取得して構造体のメンバに値を代入していきます。

この処理は、前回と同じくループで実現しますが、構造体は配列としてすでにメモリを確保してあるので、前回の構造体のリンクを使う方法よりシンプルになります。

構造体のリンクでは、コマンドラインから入力ファイルを見つけるたびに構造体のメモリを確保し、それをポインタでつないでいかなければなりませんでした。一方、今回のように配列を使った場合には、配列の添字(インデックス)を0順に加算していけば、次々と複数の入力ファイルの情報を処理していけます。

ただ、変換方向を指定するオプションの「-S/-T」を読み取って、構造体のメンバswに代入しなければならないため、switch~case文が若干複雑になっています。

ポインタをリンクした場合は、リンクの最後をNULLポインタで示すことができました。配列では、先に確保した要素未満で入力ファイルの情報が終わってしまった場合、残る配列の要素には初期化された値が入っているだけです。

これらを処理しようとすれば、当然エラーとなります(NULLポインタのファイルを開こうとした時点でエラーになります)。そこで、この“chk_opt”関数では、コマンドラインから読み取った有効な配列の最後の添字(有効な構造体の数-1)を戻り値として呼び出し元に返すようにしています。

リスト7:コマンドラインから構造体のメンバに値を代入するchk_opt関数
int     chk_opt(STCONV *p, char *argv[])
{
  int   i, sw, tabsize;
  char  * arg_p;

  ↓ 解析をARGMAX(配列の要素数)回繰り返す
  for (i = 0; (i < ARGMAX) && (*(++argv) != NULL); ) {
    arg_p = *argv;
      ↓ オプション文字列を順次処解析
    if (*arg_p == '-') {
      arg_p++;
      switch (*arg_p) {
        case 't' :
        case 'T' : ---- 「スペースをタブ」に変換
          sw = 1;
          break;
        case 's' :
        case 'S' : ---- 「タブをスペース」に変換
          sw = 0;
          break;
        default  :  
          fprintf(stderr, "-%c は無効なオプションです.¥n", *arg_p);
                 ↑ オプション文字が無効な場合はエラーメッセージを表示
      }
      arg_p++;
      tabsize = atoi(arg_p);
    }
    else {
      p->sw = sw;
      p->tab = tabsize;
      p->fname = arg_p;  入力ファイル名へのポインタを保存
      p++;
      i++;
    }
  }
  return (i); ---------- 有効な配列の最後の添字を返す
}

setdata : 構造体のメンバをセット(リスト8)

動作は前回のsetdata関数と同じです。

構造体のメンバtabの値をチェックし、それが最小値未満なら最小値に、最大値を超えていれば最大値に調整します。

その後、メンバfnameで示されるファイルをオープンし、メンバfpにファイルポインタをセットします。オープンに成功すればTRUE、失敗すればエラーメッセージを表示してFALSEを返します。

なお、前回は省略していましたが、この段階ではファイルはオープンされたままなので、プログラムの終了時にはファイルをクローズしなければなりません。クローズ処理は、後述するstconv関数内で行っています。

リスト8:構造体のメンバをセットするsetdata関数
int     setdata(STCONV *p)
{
  ↓ タブストップを適正値に調整する
  if (p->tab < MIN_TAB)
    p->tab = DEFAULT_TAB;
  else if (p->tab > MAX_TAB)
    p->tab = MAX_TAB;

  ↓ ファイルのオープンとエラー処理
  if ((p->fp = fopen(p->fname, "r")) == NULL) {
    fprintf(stderr, "%s がオープンできません.¥n", p->fname);
    return (FALSE);
  }
  return (TRUE);
}

stconv : ファイルを読み込んで1行ずつ変換(リスト9)

今回、新たに追加した関数です。

これまでは「タブ→スペース」の変換だけでしたが、今回は「スペース→タブ」への変換にも対応しています。そのため、この関数で変換方向を示す構造体のメンバswの値を調べ、「タブ→スペース変換」ならtb2sp関数、「スペース→タブ変換」ならsp2tb関数を呼び出します。

リスト9:ファイルを読み込んで1行ずつ変換するstconv関数
void    stconv(STCONV *p)
{
  char  rbuf[BUFSIZE + 1];
  char  wbuf[BUFSIZE + 1];

  ↓ ファイル名とタブストップ幅を表示
    (リダイレクト時でもディスプレイに出力されるようstderrに送る)
  fprintf(stderr, "%s¥n", p->fname);
  fprintf(stderr, "¥tタブストップ間隔 : %d / ", p->tab);

  ↓ swの値によって変換方向を切り替える
  if (p->sw == 0) { -------- タブ→スペース変換
    fprintf(stderr, "タブ → スペース¥n");
    while (fgets(rbuf, BUFSIZE, p->fp) != NULL) {
      tb2sp(rbuf, wbuf, p->tab);
      fputs(wbuf, stdout);
    }
    fclose(p->fp); -------- 最後にファイルをクローズする
  }
  else { ------------------- スペース→タブ変換
    fprintf(stderr, "スペース → タブ¥n");
    while (fgets(rbuf, BUFSIZE, p->fp) != NULL) {
      sp2tb(rbuf, wbuf, p->tab);
      fputs(wbuf, stdout);
    }
    fputs("---- end of file ----", stdout);
    fclose(p->fp); -------- 最後にファイルをクローズする
  }
}