Привет всем. Сегодня речь пойдет о задаче вычисления заданной пользователем функции. Например, мы делаем сервис для постройки графиков с возможностью задать функцию, или делаем некий аналог Excel, в котором по заданным формулам необходимо провести вычисления.
Решение мы будем искать на самых простых серверных и клиентских языках - PHP и Javascript.
Итак – нам надо каким-то образом распарсить строку, введенную пользователем. При этом убедится, что строка – настоящая формула. Мы можем при помощи хитрых манипуляций разбить строку на операции, функции, пытаться вычислить вложенность, найти сопоставление уже созданным математическим функциям и найти ответ. Все это слишком сложно. Для данных языков – существует злой оператор eval().
Eval– позволяет исполнить произвольную строку. Зло этой функции заключается в том, что пользователь может в качестве строки указать много вредоносного кода и таким образом получить доступ, или обрушить что-нибудь на сервере. По этому – если решать задачу через eval – нужно лишь удостовериться что пользователь ввел допустимую строку.
Мы, кстати, можем также позволить пользователю ссылаться в формуле на допустимые переменные конкретного кода, а также собственные пользовательские функции. Нужно лишь предварительно объявить круг этих переменных и заранее описать функции.
Примерная формула может выглядеть следующим образом
function myfunc($x){ // пользовательская функция
return $x/4;
}
$myvar=16; // пользовательская переменная
$expr='23.5 / 0+{myvar} *sqrt({myvar}/4)+myfunc({myvar})';
В формуле – мы заранее говорим пользователю, что переменную можно использовать только через кавычки. Это позволит нам не допустить ошибок, связанных со слиянием переменных с функциями, или действиями.
Деление на ноль допустимо в PHP, оно просто вернет 0. Остальные операторы будут суммироваться отдельно. На Javascript - деление на 0 выдаст бесконечность, но случай частный и не несет никакой смысловой нагрузки, главное чтобы программа не спотыкнулась, а продолжила свое выполнение.
Чтобы выполнить проверку – мы можем прибегнуть к регулярным выражениям, узнав, содержим ли формула только допустимые символы, или что-то еще. Но это долго с точки зрения вычислений. Мы можем пойти проще. Можем заменить все разрешенные конструкции на пустышки и сравнить в конце строку с пустотой. Если символы остаются – значит – ошибка, введены запрещенные символы. Мы также можем проверить на число открытых и закрытых скобок. Можем выполнить еще ряд проверок, но для безопасности хватит и этого. Если что-то в формуле не задастся - будет выведен пустой результат.
Для реализации вышеследующего – перечислим все разрешенные конструкции. Только порядок замены должен идти от общего к частному. То есть сначала – наиболее общие выражения, например «asin(», затем «sin(» , затем «(» .
$test=strtr($expr,array(
'{myvar}'=>'', // разрешенные переменные
'myfunc('=>'', // разрешенные пользовательские функции
'1'=>'',
'2'=>'',
'3'=>'',
'4'=>'',
'5'=>'',
'6'=>'',
'7'=>'',
'8'=>'',
'9'=>'',
'0'=>'',
'.'=>'',
'+'=>'',
'-'=>'',
'*'=>'',
'/'=>'',
'%'=>'',
'asin('=>'',
'sin('=>'',
'acos('=>'',
'cos('=>'',
'abs('=>'',
'ceil('=>'',
'exp('=>'',
'floor('=>'',
'tan('=>'',
'round('=>'',
'sqrt('=>'',
')'=>'',
'('=>'',
' '=>'',
));
if ($test!='')die('Ошибка. Запрещенные символы '.$test);
if (substr_count($expr,'(')!=substr_count($expr,')'))die('Ошибка. Число открытых и закрытых скобок не совпадает');
Вот и все проверки. Остается только произвести замену нашей переменной и выполнить злую операцию вычисления
$expr=strtr($expr,array(
'{inc}'=>'$inc',
'++'=>'',
'--'=>'',
));
eval('$result='.$expr.';');
echo $result;
++ и -- мы запрещаем по причине, что они изменяют исходную переменную, что может быть недопустимо. На . В начале строки проверку можно не делать, т.к. изначально строка будет пустая и мы будет ее очищать если будет использоваться цикл.
Можно использовать это как угодно, загнать в функцию, в цикл. Для Javascript все аналогично.
Успехов в создании идеальных сервисов.