<?php
mb_internal_encoding
('UTF-8');
set_time_limit(0);
ini_set('memory_limit''256M');

if (
$_SERVER['argc'] > 1) {
    die(
B_file($_SERVER['argv'][1]));
}

// мои файлы для работы с UTF-8, их надо пропустить
$skip = array('Utf.php''UtfReverseString.php''UtfString.php');

// уровень на котором работать не нужно — текущий уровень,
// файлы на этом уровне будут пропущены (внимание!)
$skipdir dirname(__FILE__);

$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('.'));

for (;
$it->valid(); $it->next()) {
    
    if (
dirname(realpath($it->key())) == $skipdir || in_array($it->getBaseName(), $skiptrue)) {
        continue;
    }

    
$ext pathinfo($it->getBaseName(), PATHINFO_EXTENSION);

    if (
$ext == 'php' || $ext == 'tpl' || $ext == 'inc') {

        echo 
$it->key(), "\n";
        
file_put_contents($it->key(), B_file($it->key()));
    }
}

/*
    обработка единичного файла
*/
function B_file($filename)
{
    
$content file_get_contents($filename);

    
$content iconv('cp1251''utf8'$content);
    
$content B_main($content);
    
$content iconv('utf8''cp1251'$content);

    return 
B_dirty_hack($content);
}

/*
    грязный хак одного из багов парсера,
    надо ещё в выходном коде поиска ::Utf::get
*/
function B_dirty_hack($str)
{
    return 
strtr(
        
$str
        array(
            
'self::Utf::get(' => 'Utf::get(self::',
    ));
}

/*
    точка входа. Есть проблема с ${a[1]} вне кавычек,
    но такой конструкции вне кавычек не бывает.
*/
function B_main($str, &$tech null)
{
    
mb_internal_encoding('UTF-8');
    
$tokens B_tok_fix_finish(token_get_all(B_tok_fix($str)));

    return 
B_replace($tech B_ren($tokens));
}

/*
    функция отладки
*/
function B_debug($mix)
{
    echo 
'<plaintext style="font-size: 10px">'var_dump($mix); exit;
}

/* 
   разбиваю токены, которые PHP почему-то сам не разбивает
   Хха, кажется не нужно этого делать. $∩ — это не $n, это другой символ
*/
function B_tok_fix($str)
{
    
/*$str = str_replace("'", '{$∩}\'{$∩}', $str);
    $str = str_replace('][', ']{$∩}[', $str);
    $str = str_replace(']{', ']{$∩}{', $str);*/

    
return $str;
}

/*
   убираем {$∩}, которые я применяю для того, чтобы разбить токены,
   удаляю пустые токены, заменяю "${var}" на "{$var}", потому что
   последнюю конструкцию потом проще обрабатывать.

   А ещё собираю разбитые токены апостроф-что-то-внутри-апостроф
   в один токен.
*/
function B_tok_fix_finish(array $arr)
{
    
$out = array();
    
$state $skip $catch false;
    
$skipped = array();

    foreach (
$arr as $token)
    {
        if (
$skip) {    
            
$skip false;
        } else {
            list(
$name$value) = is_scalar($token) ? array('STRING'$token) : $token;

            if (
$value == '{') {
                
$state '{';
                
$skipped $token;
            }
            elseif (
$name == T_DOLLAR_OPEN_CURLY_BRACES) {
                
$state '${';
            }
            elseif (
$name == T_STRING_VARNAME) {
                if (
$state == '${') {
                    
$out array_merge(
                        
$out,
                        array(
                            array(
T_CURLY_OPEN'{'),
                            array(
T_VARIABLE'$' $value),
                        )
                    );
                    
$state false;
                    continue;
                }
            }
            elseif (
$name == T_VARIABLE) {
                if (
$state == '{') {
                    if (
$token[1] == '$∩') {
                        
$state false;
                        
$skip true;
                        continue;
                    } else {
                        
$out[] = $skipped;
                        
$skip $state false;
                    }
                }
            }
            elseif (
$value == "'") {
                if (
$catch !== false) {
                    
$token = array(T_ENCAPSED_AND_WHITESPACEstr_replace('{$∩}'''$catch "'"));
                    
$catch false;
                } else {
                    
$catch "'";
                    continue;
                }
            }
            elseif (
$value == '') {
                continue;
            }
            elseif (
$state) {
                
$out[] = $skipped;
                
$skip $state false;
            }

            if (
$state === false) {
                if (
$catch === false) {
                    
$out[] = $token;
                } else {
                    
$catch .= $value;
                }
            }
        }
    }

    foreach (
$out as &$token) {
        if (
is_array($token)) {
            
$token[1] = str_replace('{$∩}'''$token[1]);
        } else {
            
$token str_replace('{$∩}'''$token);
        }

        unset(
$token);
    }

    
reset($out);

    return 
$out;
}

/*
   замена в цикле конструкций взятия по индексу, начиная с тех, у которых нет вложенных.
   Кроме того грязные фиксы вложенных замен {{…}} → {…}

   впереди не могут идти констуркции isset, unset, empty и взятие по ссылке
   сзади не могут быть ++, --, += и им подобные
*/
function B_replace($str)
{
    
$regex '(& | (?: (isset | unset | empty) \(\s* ) )?
        (?(?=\x00«)
            \x00«(?:(?!\x00[«»„“]).)+\x00» (?: \s*\+\+ | \s*\-\- | (?: \s* (?: \+ | \- | \* | / | \. | % | & | \| | ^ | << | >> )? =) )? |
                (?=\x00„)
                    \x00„(?:(?!\x00[«»“„]).)+\x00“
        )'
;

    do {
        
$str preg_replace_callback("@$regex@xusi"'B_replace_helper'$str, -1$count);
    }
    while (
$count);

    
$str strtr(
        
$str,
        array(
            
"{\0{" => '{',
            
"\0}}" => '}',
            
"\0{"  => '{',
            
"\0}"  => '}',
        )
    );

    return 
$str;
}

/*
    замена собственно конструкций. Внутри строки и вне разные замены.
*/
function B_replace_helper(array $m)
{
    if (
in_array(substr($m[0], -1), array('=''-''+'))) {
        return 
B_clear_0(preg_replace('/\x00«|\x00»/sux'''$m[0]));
    }

    
/* isset и unset перед переменной */
    
if (isset($m[1])) {
        return 
B_clear_0(preg_replace('/\x00«|\x00»/sux'''$m[0]));
    }

    
$str mb_substr($m[0], 2mb_strlen($m[0]) - 4);

    if (
strpos($str"\0‹") === false) {
        return 
$str;
    }

    
$params B_replace_param($str);

    if (
mb_substr($m[0], 11) == '„') { // замена в строке
        
return B_clear_0("\x00{\$_ENV[0x5f3759df]->get(" $params ")\x00}");
    }
    else {
        
/*
            простая проверка — является ли второй параметр строкой, справа я всё равно
            проверяю, несмотря на то, что может быть такое +"500",
            но я надеюсь на адекватность программиста
        */
        
if (preg_match('/\x00ₑ\s*["\']\D | \D["\']\x00ₔ/xus'$params)) {
            return 
B_clear_0($str);
        }

        return 
B_clear_0('Utf::get(' $params ')');
    }
}

/*
    вычистка «хвостов» — удаляю маркеры у конструкций, замена которых не требуется
*/
function B_clear_0($str)
{
    return 
preg_replace('/\x00[‹›].|\x00[ₔₑ]/us'''$str);
}

/*
    замена второго параметра функции utf::get
*/
function B_replace_param($str)
{
    if (
preg_match('/‹(\w)\[.*?›\1\]$/xusi'$str)) {
        
/* не ясно — строка или нет […], ставим маркер второго параметра */
        
return preg_replace('/\x00‹(\w)\[((?:(?!\x00›\1\]).)*)\x00›\1\]$/xusi'", \0ₑ\$2\0ₔ"$str);
    } else {
        
/* строковая замена {…} */
        
return preg_replace('/\x00‹(\w)\{((?:(?!\x00›\1\}).)*)\x00›\1\}$/xusi'', $2'$str);
    }
}

/*
    разные маркеры для переменных внутри строки и вне
*/
function B_embrace($str$in_quotes false)
{
    if (
$in_quotes) {
        return 
"\0„" $str "\0“";
    } else {
        return 
"\0«" $str "\0»";
    }
}

/*
    основная функция, расставляющие маркеры внутри конструкций,
    максимальная вложенность конструкций — 36
*/
function B_ren(array &$tokens$in_quotes false$level 0)
{
    
$pairs = array(
        
'(' => ')',
        
'{' => '}',
        
'[' => ']',
    );

    
$state false;
    
$body  '';
    
$inner_body = array();
    
$stack = array();
    
$out '';

    
$was_quotes $prev false;

    while (list(,
$token) = each($tokens)) {
        
$token = list($type$value) = is_array($token) ?
            array(
is_int($token[0]) ? token_name($token[0]) : $token[0], $token[1]) :
            array(
'STRING'$token);

        if (!
$state) {
            switch (
$type) {
                case 
'STRING':
                    if (
$value == '$') {
                        
$state 'START';
                    }
                    elseif (
$value == '"') {
                        
$was_quotes = !$was_quotes;
                    }
                    break;

                case 
'T_STRING':
                    if (
is_array($nexttoken current($tokens)) && $nexttoken[0] == T_DOUBLE_COLON) {
                        
$state 'TAIL';
                    }
                    break;


                case 
'T_DOLLAR_OPEN_CURLY_BRACES'// ${
                    
$state 'INDEX';
                    
$stack[] = '}';
                    break;

                case 
'T_VARIABLE':
                    
$state 'TAIL';
                    break;
            }

            if (
$state) {
                
$body .= $value;
            } else {
                
$out .= $value;
            }

        } else {
            if (
$state == 'START') {
                if (
$type == 'STRING' || $type == 'T_STRING') {
                    if (
preg_match('/^[a-zA-Z0-9_\x7f-\xff]+$/s'$value)) {
                        
$state 'TAIL';
                        
$body .= $value;
                    }
                    elseif (
$type == 'STRING' && $value == '{') {
                        
$state 'INDEX';
                        
$stack[] = '}';
                        
$body .= $value;
                    } else {
                        
$state false;
                        
$out .= B_embrace($body$in_quotes);
                        
$body $body_inner '';
                    }
                }
                elseif (
$type == 'T_STRING_VARNAME') {
                    
$state 'INDEX';
                    
$body .= $value;
                    
$stack[] = '}';
                }
                else {
                    
$body .= $value;
                    
$state 'TAIL';
                }

            }
            elseif (
$state == 'TAIL') {
                if (
$type == 'T_OBJECT_OPERATOR' || $type == 'T_DOUBLE_COLON') {
                    
$state 'START';
                }
                else switch (
$value) {
                    case 
'[' :
                    case 
'{' :
                        
$stack[] = $pairs[$value];
                        
$body .= "\0‹" base_convert($level1036);
                        
$state 'INDEX';
                        break;

                    default:
                        
$state false;
                        
$out .= B_embrace($body$in_quotes || $was_quotes);
                        
$body $body_inner '';

                        if (
$value == '$' || $type == 'T_DOLLAR_OPEN_CURLY_BRACES' || $type == 'T_VARIABLE') {
                            
prev($tokens);
                            
$prev true;
                        }

                        if (
$value == '"') {
                            
$was_quotes = !$was_quotes;
                        }
                }

                if (
$prev) {
                    
$prev false;
                } else {
                    if (
$state) {
                        
$body .= $value;
                    } else {
                        
$out .= $value;
                    }
                }
            }
            elseif (
$state == 'INDEX' || $state == 'INDEX-QUOTE' ) {

                if (
$state == 'INDEX') {
                    switch (
$value) {
                        case 
'[':
                        case 
'{':
                        case 
'(':
                            
$stack[] = $pairs[$value];

                            
/*if ($value == '[' || $value == '{') {
                                $inner_body[] = array('START_OF', "\0‹" . base_convert($level, 10, 36));
                            }*/

                            
break;

                        case 
']':
                        case 
'}':
                        case 
')':
                            if (
array_pop($stack) == $value) {
                                if (!
$stack) {
                                    
$state 'TAIL';
                                }

                                if (
$value == ']' || $value == '}') {
                                    
$inner_body[] = array('END_OF'"\0›" base_convert($level1036));
                                }

                            } else {
                                
trigger_error("Unpaired $value"E_USER_ERROR);
                            }
                            break;

                        case 
'"':
                            
$state 'INDEX-QUOTE';
                            
$was_quotes true;
                            break;
                    }
                }
                elseif (
$state == 'INDEX-QUOTE') {
                    if (
$value == '"') {
                        
$state 'INDEX';
                        
$was_quotes false;
                    }
                }

                if (
$state <> 'INDEX' && $state <> 'INDEX-QUOTE') {
                    
$body .= B_ren($inner_body$was_quotes$level 1) . $value;
                    
$inner_body = array();

                    if (
$state == 'INDEX-QUOTE') {
                        
$was_quotes false;
                    }
                } else {
                    
$inner_body[] = $token;
                }
            }
        }

        if (
defined('B_DEBUG') && B_DEBUG) {
            echo 
'<pre style="font:10px">',
                
htmlspecialchars(var_export(array_merge($token, array($state)), true)),
                
'</pre>';
        }
    }

    if (
$body !== '') {
        
$out .= B_embrace($body$in_quotes);
    }

    return 
$out;
}