こんにちは、高木です。
前回は違いましたが、今回からPHPでC言語の前処理を行う話題に戻ります。
今回は、文字列リテラルのエンコーディングを静的に変更してしまおうという試みです。ソースコード全体のエンコーディングを変更するのであれば、たとえばGCCであれば-fexec-charsetオプションをコンパイル時に付けることで対応できます。処理系によって、やりやすさには違いがありますが、まあ何とかなるでしょう。ただ、そうしたやり方では、翻訳単位ごとにエンコーディングを切り替えるのが限度です。
そうではなく、文字列リテラルごとにエンコーディングを切り替えたいことがあります。実行時に変換するのであれば、libiconvを使うなどの方法で対応することもできます。しかし、実行時に変換していては処理時間のコストが馬鹿になりません。そこで、せっかくPHPで前処理するのだから、静的に文字列リテラルごとにエンコーディングを変更できるようにしようと考えました。
PHPには、mb_convert_encoding関数という、文字通りエンコーディングを変換するための機能が備わっています。ただ、この関数の結果をそのままC言語のソースコードとして出力してもうまくいきません。mb_convert_encoding関数の結果はバイナリデータだと考えた方がよく、たとえば逆斜線にあたる0x5cが含まれていたり、二重引用符にあたる0x22が含まれていたりすると破綻してしまうことでしょう。
今回は、C言語のソースコードに適した形で出力できるように、\xhh形式の並びに変換して出力することにしました。また、前後に二重引用符を付加することで、そのまま文字列リテラルとして使えるようにしようと思います。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?php $default_output_encoding = 'UTF-8'; function text(string $s, string $to_encoding = NULL, string $from_encoding = NULL): string { global $default_output_encoding; if (is_null($to_encoding)) $to_encoding = $default_output_encoding; $s = mb_convert_encoding($s, $to_encoding, $from_encoding); $r = ''; foreach (unpack('C*', $s) as $c) { $r .= sprintf("\\x%02x", $c); } return "\"$r\""; } ?> |
上記で定義しているtext関数が、エンコーディングを指定して文字列リテラルを生成するためのものです。出力するエンコーディングと入力するエンコーディングを両方指定できるようにしていますが、入力するエンコーディングはデフォルトのままで普通は大丈夫でしょう。出力するエンコーディングは明示的に指定することもできますが、あらかじめグローバル変数の$default_output_encodingに設定することで省略できるようにしています。
今回は文字列の各文字を数値として取り出すのにunpack関数を使ってみました。バイナリデータの扱いは、文字列から[]演算子で1文字ずつ切り出してからord関数を適用するより、この方が効率がよさそうです。
\xhh形式に変換するにはPHPのsprintf関数を使ってみました。細かいことを気にしなければ大体C言語のsprintf関数と同じように使えるのですが、結果の文字列を戻り値として返せるのが便利ですね。
今回作成したtext関数を拡張すれば、エンコーディングだけではなく言語の翻訳も可能になりそうです。ただ、printf系関数の書式に使う場合には、翻訳後の言語によっては語順の問題が出てくるのでもう少し工夫が必要になりそうです。