mb_ereg_replace_callback()

PHP のマルチバイト正規表現関数には置換にコールバック関数を使えるがもの無いので、作ってみた。
preg_replace_callback() とは微妙に挙動を変えてあり、$limit に負の数を指定したときは、最後から順番に n 番目にマッチした箇所まで置換するようにしてあるんだけど、それができて嬉しい人は極めて稀だと思う。
僕は UTF-8 で preg_replace_callback() を使うのを好むので、もしかするとこれが日の目を見ることはないかもしれないけど、mb_ereg_search_* 系関数の使い方を覚えたのは収穫だった。

function mb_ereg_replace_callback($pattern, $callback, $string, $limit = null, $option = null)
{
    // $limit の挙動を preg_replace_callback() 互換にするか否か
    $compat_preg_replace_callback = false;

    // マルチバイト正規表現関数のデフォルトオプションを取得する
    if (is_null($option)) {
        $option = mb_regex_set_options();
    }

    // マッチ回数制限の前処理
    if (!is_null($limit)) {
        $limit = intval($limit);
        if ($limit < 0) {
            if ($compat_preg_replace_callback) {
                $limit = ($limit == -1) ? null : 0;
            } else {
                $back = $limit;
                $limit = null;
            }
        }
    }

    // マルチバイト正規表現検索する
    mb_ereg_search_init($string, $pattern, $option);
    $m = array();
    $i = 0;
    while (($p = mb_ereg_search_pos()) !== false && (is_null($limit) || $i < $limit)) {
        $r = array(
            'offset' => $p[0],
            'length' => $p[1],
            'regs'   => mb_ereg_search_getregs()
        );
        array_push($m, $r);
        $i++;
    }

    // マッチ回数制限が負の数のときの後処理
    if (isset($back) && $i > abs($back)) {
        array_splice($m, 0, $back);
    }

    // 最後にマッチした箇所から置換する
    $result = '';
    while (($r = array_pop($m)) !== null) {
        $result = call_user_func($callback, $r['regs'])
                . mb_strcut($string, $r['offset'] + $r['length'])
                . $result;
        $string = mb_strcut($string, 0, $r['offset']);
    }

    return $string . $result;
}