コマンドライン引数解析関数を生成する gperf

プロファイラかと思ってたら全然違ってたのでメモ。
getopt するよりシンプルでいいな。
gperf を使って効率的に C/C++ コマンド・ラインを処理する

こんなファイルを用意する。

diff.gperf

%{
typedef struct CommandOption_ CommandOption;
enum CommandOptionCode {
IGNORE_CASE,
IGNORE_FILE_NAME_CASE,
IGNORE_TAB_EXPANSION,
IGNORE_SPACE_CHANGE,
IGNORE_ALL_SPACE,
IGNORE_BLANK_LINES,
};
%}
struct CommandOption_
  {
  const char *name;
  int code;
  };
%%
-i,IGNORE_CASE
--ignore-case,IGNORE_CASE
--ignore-file-name-case,IGNORE_FILE_NAME_CASE
-E,IGNORE_FILE_NAME_CASE
--ignore-tab-expansion,IGNORE_TAB_EXPANSION
-b,IGNORE_SPACE_CHANGE
--ignore-space-change,IGNORE_SPACE_CHANGE
-w,IGNORE_ALL_SPACE
--ignore-all-space,IGNORE_ALL_SPACE
-B,IGNORE_BLANK_LINES
--ignore-blank-lines,IGNORE_BLANK_LINES

diff.c

#include <stdio.h>
#include <string.h>
#include "diff_option.c"

int main(int argc, char* argv[]) {
  if (argc == 1) {
    puts("options:");
    for (int i = 0; i < TOTAL_KEYWORDS; ++i) {
      printf("\t%s\n", wordlist[i].name);
    }
    return 1;
  }
  
  for (int i = 1; i < argc; ++i) {
    const CommandOption *option = in_word_set(argv[i], strlen(argv[i]));
    switch (option->code) {
    case IGNORE_CASE:
      puts("Ignore case differences in file contents.");
      break;
    case IGNORE_FILE_NAME_CASE:
      puts("Ignore case when comparing file names.");
      break;
    case IGNORE_TAB_EXPANSION:
      puts("Consider case when comparing file names.");
      break;
    case IGNORE_SPACE_CHANGE:
      puts("Ignore changes in the amount of white space.");
      break;
    case IGNORE_ALL_SPACE:
      puts("Ignore all white space.");
      break;
    case IGNORE_BLANK_LINES:
      puts("Ignore changes whose lines are all blank.");
      break;
    }
  }
  
  return 0;
}

作る

$ gperf -CGD -LANSI-C -t diff.gperf > diff_option.c
$ gcc -std=c99  -Wall diff.c -o diff
$ ./diff
options:
        -w
        -B
        -i
        -b
        --ignore-case
        -E
        --ignore-all-space
        --ignore-blank-lines
        --ignore-space-change
        --ignore-tab-expansion
        --ignore-file-name-case
$ ./diff -w
Ignore all white space.