プログラム言語毎の正規表現の違い
Intact Case のライブラリでは、 camelCase と snake_case の変換に正規表現を使っています。 PHP と Ruby と Javascript で正規表現の仕様が異なり、それぞれのプログラミング言語用の変換パターンを作成する必要がありました。本記事内では、 Javascript は ECMAScript5 として記載しています( 2015年 7月現在)。
| Pattern | Kind | PHP | Ruby | Javascript |
|---|---|---|---|---|
| (?=) | 先読み言明 | ✔ | ✔ | ✔ |
| (?!) | ✔ | ✔ | ✔ | |
| (?<=) | 後読み言明 | ✔ | ✔ | none |
| (?<!) | ✔ | ✔ | none | |
| (?>) | アトミックグループ | ✔ | ✔ | none |
| ?? | 最短一致 | ✔ | ✔ | ✔ |
| *? | ✔ | ✔ | ✔ | |
| +? | ✔ | ✔ | ✔ | |
| ?+ | 最長一致 | ✔ | ✔ | none |
| *+ | ✔ | ✔ | none | |
| ++ | ✔ | ✔ | none |
Javascript は「後読み言明」がないため、複雑なマッチパターンを表現するときに工夫が必要です。
正規表現のバグ
Intact Case のライブラリを実装している際、プログラミング言語によって同一のマッチパターンが期待通りの結果にならないケースがありました。調査した結果、マッチパターンの OR 条件の仕様が異なるのが原因だと分かりました。
以下の条件にマッチする正規表現で文字列変換を行った場合に、期待される結果を想定してみます。 Javascript には後読みがないので、 (?<=) を使わないマッチパターンを使います。
以下の場所に @ を追加する
- 文字列全体の先頭
- Abc の直後
| マッチパターン | /^|(Abc)/g |
|---|---|
| 変換文字列 | "$1@" |
| 対象文字列 | AbcAbcAbc |
| 期待される結果 | @Abc@Abc@Abc@ |
コード
| PHP | preg_replace('/^|(Abc)/', '$1@', 'AbcAbcAbc'); |
|---|---|
| Javascript | "AbcAbcAbc".replace(/^|(Abc)/g, "$1@"); |
| Ruby | 'AbcAbcAbc'.gsub(/^|(Abc)/) { "#{$1}@" } |
プログラミング言語毎の実行結果です。 Javascript と Ruby は、期待通りの変換結果が得られませんでした。
| PHP | @Abc@Abc@Abc@ |
|---|---|
| Javascript | @AbcAbc@Abc@ |
| Ruby | @AbcAbc@Abc@ |