オフラインリアルタイムどう書く E29 の実装例( C99)

問題の名前 : アンエスケープ
問題 : http://nabetani.sakura.ne.jp/hena/orde29unes/
実装リンク集 : https://qiita.com/Nabetani/items/f2db9b916c0a301b744f

次回のイベントは  2月2日
see https://yhpg.doorkeeper.jp/events/84247 。

で。
C言語アドベントカレンダーに空きがあったので、C99で書いてみた。

```C:C99
// clang -Wall -std=c99 e29.c
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

enum mode { normal, dq, sq, slash };

// strdup は標準じゃないので定義する
char *dup(char const *src) {
  size_t len = strlen(src) + 1;
  char *p = malloc(len);
  memcpy(p, src, len);
  return p;
}

typedef struct entry {
  char *p;
} entry;

typedef struct entries {
  entry *p;
  size_t size;
} entries;

void entries_add_empty_entry(entries *ents) {
  ++ents->size;
  if (ents->p) {
    ents->p = realloc(ents->p, sizeof(entry *) * ents->size);
  } else {
    ents->p = malloc(sizeof(entry *));
  }
  ents->p[ents->size - 1].p = dup("");
}

void entries_free(entries ents) {
  for (size_t i = 0; i < ents.size; ++i) {
    free(ents.p[i].p);
  }
  if (ents.p) {
    free(ents.p);
  }
}

void entries_append_char(entries *ents, char ch) {
  entry *e = &ents->p[ents->size - 1];
  size_t len = strlen(e->p);
  e->p = realloc(e->p, len + 2);
  e->p[len] = ch;
  e->p[len + 1] = 0;
}

enum mode do_normal(entries *ents, char ch) {
  switch (ch) {
  case '/':
    return slash;
  case '\'':
    return sq;
  case '"':
    return dq;
  default:
    entries_append_char(ents, ch);
    return normal;
  }
}
enum mode do_slash(entries *ents, char ch) {
  switch (ch) {
  case '/':
    entries_append_char(ents, ch);
    return normal;
  case '\'':
    entries_add_empty_entry(ents);
    return sq;
  case '"':
    entries_add_empty_entry(ents);
    return dq;
  default:
    entries_add_empty_entry(ents);
    entries_append_char(ents, ch);
    return normal;
  }
}

enum mode do_quote(entries *ents, char q, enum mode mode, char ch) {
  if (ch == q) {
    return normal;
  } else {
    entries_append_char(ents, ch);
    return mode;
  }
}

entries unescape(char const *src) {
  entries ents = {0, 0};
  entries_add_empty_entry(&ents);
  enum mode mode = normal;
  for (char const *p = src; *p; ++p) {
    switch (mode) {
    case normal:
      mode = do_normal(&ents, *p);
      break;
    case slash:
      mode = do_slash(&ents, *p);
      break;
    case dq:
      mode = do_quote(&ents, '"', mode, *p);
      break;
    case sq:
      mode = do_quote(&ents, '\'', mode, *p);
      break;
    default:
      fputs("logic error", stderr);
      exit(1);
    }
  }
  if (mode == normal) {
    return ents;
  } else {
    entries_free(ents);
    entries empty_ents = {0, 0};
    return empty_ents;
  }
}

// caller should free return value memory.
char const *solve(char const *src) {
  entries ents = unescape(src);
  size_t len = ents.size; // コンマの数と null terminator
  for (size_t i = 0; i < ents.size; ++i) {
    len += strlen(ents.p[i].p);
  }
  char *str = (char *)calloc(len + 1, 1);
  bool okay = 0<ents.size;
  for (size_t i = 0; okay && i < ents.size; ++i) {
    if (i != 0) {
      strcat(str, ",");
    }
    if (0 == *ents.p[i].p) {
      okay = false;
    }
    strcat(str, ents.p[i].p);
  }
  entries_free(ents);
  if (okay) {
    return str;
  } else {
    free(str);
    return dup("-");
  }
}

struct result {
  int success;
  int testcount;
};

void test_(struct result *r, char const *src, char const *expected) {
  char const *actual = solve(src);
  int okay = 0 == strcmp(actual, expected);
  if (okay) {
    ++r->success;
  }
  ++r->testcount;
  printf("%s : %s->%s(%s)\n", (okay ? "ok" : "**NG**"), src, actual, expected);
  free((void *)actual);
}

int main(void) {
  struct result r = {0};
#define test(src, expected) test_(&r, src, expected)
  /*0*/ test("foo/bar/baz", "foo,bar,baz");
  /*1*/ test("/foo/bar/baz'/", "-");
  /*2*/ test("\"", "-");
  // 中略
  /*63*/ test("Foo/Bar/\"Hoge'/'Fuga\"", "Foo,Bar,Hoge'/'Fuga");
#undef test
  printf("%d / %d\n", r.success, r.testcount);
  return r.testcount == r.success ? 0 : 1;
}
```

文字列処理が中心の問題なので、C言語は割と辛かった。
メモリリークしていないかどうか、ちょっと自信がない。

`realloc` しまくっているし、`strcat` や `strlen` でなめまくっているので、だいぶ遅いと思う。
しかしなんで `strdup` は標準じゃないんだろう。便利なんだけどなぁ。
`realloc_cat` みたいな関数もあったっていいと思う。

せっかくなので、C11 を名乗って `strcpy_s` を使おうと思ったんだけど、手元の clang も gcc-8 もそんな関数ないよというエラーになった。そういうものか。