Завершаем изучение регулярных выражений в php-скриптах

 | 17.51

Мой Компьютер, №11 (515), 28.07.2008

Эпизод 4. А поподробнее можно?

Пока что мы только проверяли соответствие строки паттерну и извлекали из текста все совпадения. Однако возникают и ситуации, в которых надо не просто проверить, содержится ли в строке искомый образец, но также получить, где именно он находится (найти позицию, с которой начинается совпадение). Такая возможность часто требуется, например, при написании различных парсеров.

(А вот парсер, если кому некогда заглянуть в справочную Сеть, это синтаксический анализатор — программа или часть программы, выполняющая синтаксический анализ. — Прим. ред.)

Функции preg_match() и preg_match_all() могут возвращать для каждого найденного совпадения номер позиции, но по умолчанию данная возможность выключена. Для ее включения используется специальный флаг PREG_OFFSET_CAPTURE, который указывается после массива с результатами:

preg_match ($pattern, $string, $matches, PREG_OFFSET_CAPTURE);

preg_match_all ($pattern, $string, $matches, PREG_OFFSET_CAPTURE);

При включенном флаге меняется и структура массивов-результатов.

preg_match() вернет массив следующей структуры:

$matches = array(

0=>array( // совпадение

0 => ”………………………», //текст совпадения

1 => n      // позиция

)

);

А preg_match_all() — такую структуру:

$matches = array(

0=>array(    // совпадения

0 => array( // первое совпадение

0 => ”………………………», //текст совпадения

1 => n      // позиция

),

1 => array( // второе совпадение

0 => ”………………………», //текст совпадения

1 => n      // позиция

),

……………………………………………………………………………………………………. // и т.д.

)

);

Номер возвращаемой позиции содержит порядковый номер первого символа, с которого началось совпадение с паттерном. Напомню, что как и в языке С, нумерация символов начинается с нуля.

Ниже приведен код демонстрационного скрипта example5.php, который реализует простейший парсер арифметических выражений. Скрипт получает введенное в форму выражение и вычисляет его значение. Поддерживаются целые числа, четыре арифметические операции (с учетом приоритета умножения и деления над сложением и вычитанием) и скобки. Для разбиения выражения на операторы и операнды используются функции preg_match() и preg_match_all() с включенным флагом PREG_OFFSET_CAPTURE.

<?php

// функция получения «атома»

// один «атом» — элемент арифметического выражения:

// операнд, оператор или скобка

function get_atom($str, &$pos)

{

// выполняем поиск первого совпадения в строке

// паттерн задает одну из трех альтернатив:

// — либо последовательность десятичных цифр d+

// — либо открывающая скобка (

// — либо знак арифметической операции [+-*/]

preg_match(«/(|d+|[+-*/]/», $str, $m, PREG_OFFSET_CAPTURE);

// позиция

$pos = $m[0][1];

// найденный «атом»

return $m[0][0];

}

// функция находит для скобки позицию

// соответствующей ей закрывающей скобки

// с учетом вложенности скобок

function find_close_bracket($str)

{

// переменная, куда будет записываться позиция

$pos = 0;

// глубина вложенности скобок

$depth = 0;

// получаем все скобки, начиная с позиции,

// открывающей скобки, для которой ищем закрывающую

preg_match_all(«/(|)/», $str, $m, PREG_OFFSET_CAPTURE);

// идем циклом по найденным совпадениям

foreach ($m[0] as $key=>$value)

{

// на каждом шаге сохраняем значение позиции

$pos = $value[1];

// если встретили открывающую скобку — увеличиваем глубину вложенности

if ($value[0] == «(«) $depth++;

// если встретили закрывающую скобку — уменьшаем глубину вложенности

if ($value[0] == «)») $depth—;

// если после очередного изменения глубины

// ее значение ноль — искомая скобка найдена

if ($depth == 0) break;

}

// возвращаем позицию

return $pos;

}

// если в начале выражения стоит знак минус,

// это значит, что первое число отрицательное

// также в начале выражения может стоять

// открывающая скобка

// любые другие символы надо вырезать

// это делает данная функция

function trim_arr(&$arr)

{

// если первый элемент массива с последовательностью «атомов» выражения

// не число, не знак минус и не скобка,

// то вырезаем его

if (!preg_match(«/d+|-|(/», $arr[0])) unset($arr[0]);

}

// функция принимает массив с «атомами» выражения

// и выполняет мультипликативные операции (умножение, деление)

function multiplicative (&$expr)

{

// указатель на начало массива

reset($expr);

// получили первое значение

$value = current($expr);

// идем циклом по массиву

do

{

// если текущий элемент содержит операцию

if (($value == «*») or ($value == «/»))

{

// здесь мы возьмем предыдущий и следующий элементы,

// которые содержат операнды, и выполним операцию

// ключ текущего элемента

$key = key($expr);

// получаем первый операнд — предыдущий элемент массива

$op1 = prev($expr);

// получили ключ предыдущего элемента массива

$prevkey = key($expr);

// двигаемся вперед обратно к текущему элементу

next($expr);

// и еще вперед на следующий элемент. Получаем второй операнд

$op2 = next($expr);

// и ключ следующего элемента

$nextkey = key($expr);

// не забываем со следующего элемента вернуться назад к текущему

prev($expr);

// выполняем нужную операцию

// результат помещаем в текущий элемент,

// где до этого содержался оператор

switch ($value)

{

case «*»:

$expr[$key] = $op1 * $op2;

break;

case «/»:

$expr[$key] = $op1 / $op2;

break;

}

// удаляем предыдущий и следующий

// элементы массива с операндами

unset($expr[$prevkey]);

unset($expr[$nextkey]);

}

}

while ($value = next($expr));

}

// функция принимает массив с «атомами» выражения

// и выполняет аддитивные операции (сложение, вычитание)

function additive($arr)

{

// тут будет накапливаться сумма

$sum = 0;

// тут будет храниться последний оператор

// по умолчанию полагаем, что это оператор значения

$op = «+»;

// идем циклом по массиву

foreach ($arr as $key=>$value)

{

// если текущий элемент оператор

if (($value == «-«) or ($value == «+»))

// сохраняем его в переменной

$op = $value;

else

{

// если текущий элемент — операнд,

// в зависимости от последнего оператора,

// прибавляем или отнимаем его от текущей

// суммы

switch ($op)

{

case «+»:

$sum += $value;

break;

case «-«:

$sum -= $value;

break;

}

}

}

// возвращаем сумму

return $sum;

}

// собственно рекурсивная функция,

// которая парсит выражение и

// вычисляет его значение

function calculate($str)

{

// инициализируем массив для хранения «атомов»

$expr = array();

// выполняем поиск всех «атомов» в строке

do

{

$p = -1;

// получаем первый встретившийся атом

$atom = get_atom($str, $p);

// если это скобка

if ($atom == «(«)

{

// ищем для нее закрывающую скобку

$p1 = find_close_bracket($str);

// рекурсивно вычисляем значения выражения в скобках

// и заносим его значение как операнд в массив «атомов»

$expr[] = calculate(substr($str, $p+1, $p1 — $p — 1));

// удаляем обработанную часть строки

$str = substr($str, $p1 + 1, strlen($str) — $p1 — 1);

}

elseif ($atom)

{

// если не скобка — добавляем оператор или операнд в массив атомов

$expr[] = $atom;

// удаляем обработанную часть строки

$str = substr($str, $p + strlen($atom), strlen($str) — $p — strlen($atom));

}

}

while ($atom);

// обрезаем при надобности первый элемент массива

trim_arr($expr);

// сначала выполняем мультипликативные операции,

// т.к. у них более высокий приоритет

multiplicative($expr);

// затем выполняем аддитивные операции

// и возвращаем результат

return additive($expr);

}

// если был произведен ввод в поле

if (isset($_REQUEST[«expr»]))

{

// вычисляем результат выражения

$result = calculate($_REQUEST[«expr»]);

}

?>

<form id=»checkform» name=»checkform» action=»example5.php» method=»post»>

<table width=»250″>

<tr>

Окончание на стр. 29

Окончание. Начало на стр. 26-27

<td>

Выражение:

</td>

<td>

<input type=»text» id=»expr» name=»expr» size=»15″ value=»<?php echo $_REQUEST[«expr»]; ?>»>

</td>

</tr>

<tr>

<td colspan=»2″ align=»center»>

<input type=»submit» value=»Вычислить»>

</td>

</tr>

</table>

</form>

<?php

// если был произведен ввод в поле

if (isset($_REQUEST[«expr»]))

{

?>

<br><b>Результат: </b>

<?php

// выдаем результат

echo $result;

}

?>

В качестве «домашнего задания» предлагаю заинтересовавшимся читателям доработать данный скрипт. Добавить поддержку дробных чисел, операцию возведения в степень, различные математические функции (синус, косинус, корень, логарифм и т.п.). Надеюсь, что у вас все получится!

Алексей «CyberAdmin» СЕРДЮКОВ

Robo User
Web-droid editor

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *