2010-03 << 2010-04 >> 2010-05

2010-04-08 (木)

*正規表現でPHPのコードをパースする

正規表現はテキスト処理の必需品ですが,構造化された文字列を処理するのはちょっと(?)難しいです.

ただ,PCREの拡張正規表現を使うと,比較的簡単にプログラムのコードのようなものから必要な情報を抜き出すことができます.

ちゃんとしたパーサがあれば使うことに越したことはありませんし,正確な情報が取れますが,手っ取り早く済ませたい場合,やはり正規表現で書くことになると思います.

$code = preg_replace('/([\"\'])([^\1\\\\]|(\\\\.))*?\1/',' _STR_ ',$code); // 文字列を置き換える
$code = preg_replace('/\/\/.*/',' _COMMENT_ ',$code); // コメントを置き換える
$code = preg_replace('/\s+/',' ',$code); // 連続する空白をスペースに
$code = preg_replace('/((?<=[^\w_])\s+(?=[\w_]))|((?<=[\w_])\s+(?=[^\w_]))/','',$code); // 余分な空白削除

preg_match_all( <<< RE_END
/
    (
        # 記号
        \{ | \} |
        # 予約語
        (?<=[^\w])
         (?:if|else|elseif|catch|try|switch|for|foreach)
        (?=[^\w])
    )
    # 予約後に続くパラメータ等
    ((?<=[\w])\s*[^;\{\}:]+)?
/x
RE_END
, $code, $match)

本当は複数行コメントとか色々ありますが,とりあえずこんな感じで書けると思います.

拡張正規表現の後方一致検索(?<=)や前方一致(?=)は正規表現の先頭や末尾でしか使わない人をたまに見かけますが,途中に入れてトークン切り出しの制約を付けると便利です.上のように書いておけば,$match[1]に記号や予約語が,$match[2]にパラメータにあたる文字列が入っているので,後々扱いやすくなります.

ただ,関数の宣言や呼びだしとか,変数への代入とかの情報も欲しくなると,恐ろしいことになるので気を付けた方が良いでしょう.複数の正規表現で書ければ良いのですが,前後関係の情報が失われてしまうのでできません.preg_系の関数がマッチした位置を列挙できれば良いのですが….

PHPプログラマっぽい日記を書きました.

2010-03 << 2010-04 >> 2010-05