問題の名前 : アンエスケープ
問題 : http://nabetani.sakura.ne.jp/hena/orde29unes/
実装リンク集 : https://qiita.com/Nabetani/items/f2db9b916c0a301b744f
次回のイベントは 2月2日
see https://yhpg.doorkeeper.jp/events/84247 。
で。
C言語アドベントカレンダーに空きがあったので、C99で書いてみた。
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 もそんな関数ないよというエラーになった。そういうものか。