Для любого вида синтаксического анализа на основе грамматики регулярные выражения обычно являются ужасным решением. Даже некоторые грамматики (например, арифметические) имеют вложенность, и именно на вложенности (в частности) регулярные выражения просто падают.
К счастью, PHP предоставляет вам гораздо лучшее решение, предоставляя вам доступ к тому же лексическому анализатору, который используется интерпретатором PHP, через функцию token_get_all () . Дайте ему символьный поток кода PHP, и он будет разбирать его на токены («лексемы»), которые вы можете выполнить с помощью довольно простого конечного автомата .
.
Запустите эту программу (она запускается как test.php, поэтому она сама пробует ее). Файл намеренно плохо отформатирован, поэтому вы можете легко с ним справиться.
<?
define('CONST1', 'value' );
define (CONST2, 'value2');
define( 'CONST3', time());
define('define', 'define');
define("test", VALUE4);
define('const5', //
'weird declaration'
) ;
define('CONST7', 3.14);
define ( /* comment */ 'foo', 'bar');
$defn = 'blah';
define($defn, 'foo');
define( 'CONST4', define('CONST5', 6));
header('Content-Type: text/plain');
$defines = array();
$state = 0;
$key = '';
$value = '';
$file = file_get_contents('test.php');
$tokens = token_get_all($file);
$token = reset($tokens);
while ($token) {
// dump($state, $token);
if (is_array($token)) {
if ($token[0] == T_WHITESPACE || $token[0] == T_COMMENT || $token[0] == T_DOC_COMMENT) {
// do nothing
} else if ($token[0] == T_STRING && strtolower($token[1]) == 'define') {
$state = 1;
} else if ($state == 2 && is_constant($token[0])) {
$key = $token[1];
$state = 3;
} else if ($state == 4 && is_constant($token[0])) {
$value = $token[1];
$state = 5;
}
} else {
$symbol = trim($token);
if ($symbol == '(' && $state == 1) {
$state = 2;
} else if ($symbol == ',' && $state == 3) {
$state = 4;
} else if ($symbol == ')' && $state == 5) {
$defines[strip($key)] = strip($value);
$state = 0;
}
}
$token = next($tokens);
}
foreach ($defines as $k => $v) {
echo "'$k' => '$v'\n";
}
function is_constant($token) {
return $token == T_CONSTANT_ENCAPSED_STRING || $token == T_STRING ||
$token == T_LNUMBER || $token == T_DNUMBER;
}
function dump($state, $token) {
if (is_array($token)) {
echo "$state: " . token_name($token[0]) . " [$token[1]] on line $token[2]\n";
} else {
echo "$state: Symbol '$token'\n";
}
}
function strip($value) {
return preg_replace('!^([\'"])(.*)\1$!', '$2', $value);
}
?>
Выход:
'CONST1' => 'value'
'CONST2' => 'value2'
'CONST3' => 'time'
'define' => 'define'
'test' => 'VALUE4'
'const5' => 'weird declaration'
'CONST7' => '3.14'
'foo' => 'bar'
'CONST5' => '6'
Это в основном конечный автомат, который ищет шаблон:
function name ('define')
open parenthesis
constant
comma
constant
close parenthesis
в лексическом потоке исходного файла PHP и обрабатывает две константы как пару (имя, значение). При этом он обрабатывает вложенные операторы define () (согласно результатам) и игнорирует пробелы и комментарии, а также обрабатывает несколько строк.
Примечание: Я сознательно сделал так, что он игнорирует случай, когда функции и переменные являются константными именами или значениями, но вы можете расширить его до этого, как пожелаете.
Стоит также отметить, что PHP довольно прост, когда дело касается строк. Они могут быть объявлены одинарными кавычками, двойными кавычками или (в определенных обстоятельствах) без кавычек вообще. Это может быть (как указал Гамбо) двусмысленная ссылка на константу, и у вас нет возможности узнать, что это такое (в любом случае это не гарантированный путь), что дает вам выбор:
- Игнорирование этого стиля строк (T_STRING);
- Проверка, была ли объявлена константа с этим именем, и замена ее значения. Нет никакого способа узнать, какие другие файлы были вызваны, и при этом вы не можете обработать какие-либо определения, которые создаются условно, поэтому вы не можете с уверенностью сказать, является ли что-то определенным константой или нет, и какое значение это имеет; или
- Вы можете просто жить с возможностью того, что это могут быть константы (что маловероятно), и просто рассматривать их как строки.
Лично я бы пошел на (1), затем (3).