Разработка

Всю сложную работу по решению уравнения возложим на процедуру solve. Процедура получает коэффициенты уравнения и его правую часть в списке параметров, и возвращает массив с числами, составляющими решение:

sub solve {  }

die "$0: Требуется несколько положительных чисел в командной строке!\n"
	unless(@ARGV);

die "$0: Не могу решить уравнение!\n" unless my @solution=solve(@ARGV);
print "@solution\n";

Приступим к программированию процедуры.

Сохраним правую часть уравнения (последний элемент массива @_) в переменной $b. Что касается массива коэффициентов в левой части, приготовим для их хранения массив @a. Эти коэффициенты понадобятся нам лишь в составе квантификаторов в регулярном выражении, причём уменьшенные на единицу. В таком виде и отправим их в массив @a. Массив @x подготовим для решения; в конце процедуры он будет возвращён:

my $b=pop @_;
my @a=map {$_-1} @_;
my @x;

Следующий фрагмент кода посвящён составлению регулярного выражения в переменной $re:

my $re='^';
$re.="(Q*)\\$_\{".$a[$_-1].'}' for 1..@a;
$re.='$';

А теперь, собственно, решение уравнения. В случае успешного сопоставления с регулярным выражением массив @x следует заполнить значениями длин переменных $1, $2, и т. д.

Вот это по-настоящему трудное место. Будь фрагменты строки, захваченные при сопоставлении с регулярным выражением, элементами массива, всё было бы очень просто: этот массив можно скопировать в массив @x. Здесь же нам потребуется перебирать переменные по их именам. Это задача была бы неразрешима, если бы программы, написанные на Perl, не имели средств для исследования собственной структуры во время своего исполнения. Такое явление называется рефлексией, или интроспекцией. Термин «рефлексия», переводящийся как «отражение», в психологии означает способность субъекта к самопознанию, самоисследованию, самокопанию. Слово «интроспекция» дословно переводится как «взор, обращённый внутрь». И то, и другое слово очень хорошо передают суть явления. Интроспективная программа может прямо во время исполнения узнать свою структуру, в частности, определить список имён своих переменных и получить доступ к этим переменным по их именам. Точно так же программа может узнать перечень имён определённых в ней процедур и вызывать их по именам.

Тут читатель может спросить: что же интересного в том, что программа обращается к переменным и вызывает процедуры по их именам? Разве это не самое обычное явление? Но дело в том, что обращаться нужно не по именам, записанным в тексте программы программистом, а по именам, вычисленным во время исполнения программы.

Вот как это делается:

$hello="HELLO!\n";
print $hello;	печатаем значение переменной $hello

$name='hello';
print ${$name};	печатаем значение переменной, чьё имя содержится в переменной $name

Другое средство интроспекции — использование встроенной процедуры eval. Она, помимо прочего, может запустить код на языке Perl, переданный в виде строки в эту процедуру как параметр.

eval "print \$$name;";

В этом примере процедуре eval передаётся строка print $hello; (это значение выражения "print \$$name;").

Применим оба этих средства интроспекции для решения нашей частной задачи — организации перебора переменных $1, $2, …:

if(('Q' x $b)=~m/$re/)
{
	push @x, length ${$_} for 1..@a;
}

или

if(('Q' x $b)=~m/$re/)
{
	eval "push @x, length \$$_;" for 1..@a;
}

Завершит тело процедуры solve возврат массива @x:

return @x;

[Примечание]Примечание

Интроспекция не является основным приёмом программирования на языке Perl. Оба фрагмента (особенно второй) демонстрируют программирование на грани хулиганства. Однако по-другому в данной задаче никак.

Интроспекция — чрезвычайно мощное и при этом чрезвычайно опасное средство. Программы, меняющие свою структуру, свою логику во время исполнения, нужны для реализации искусственного интеллекта. Такие программы можно сделать адаптивными, то есть наделить способностью к приспособлению в меняющихся условиях, подобно вирусу, перестраивающему свой организм для сохранения живучести в нашем сложном мире.

В то же время программа, способная исполнять код, неизвестный в момент программирования, может быть по-настоящему опасной. Представьте такую программу dangerous-eval.pl:

#!/usr/bin/perl

use warnings;

print eval shift, "\n";

Она берёт из командной строки параметр — заданный пользователем текст, и затем исполняет его как код на Perl, выводя на печать результат. С помощью такой программы пользователь может делать всё что угодно, даже если она предназначалась, по замыслу программиста, для решения какой-то частной задачи (например, для вычисления арифметических выражений, заданных в командной строке):

% ./dangerous-eval.pl 'sqrt(5**2+12**2)' 13 пользователь вычислил выражение — очень хорошо % ./dangerous-eval.pl 'unlink "MyVeryImportantFile"' 1 ой-ой-ой, теперь пользователь удалил важный файл % ./dangerous-eval.pl 'open $p, "/etc/passwd"; print <$p>' root:x:0:0:root:/root:/bin/zsh bin:x:1:1:bin:/bin:/bin/sh daemon:x:2:2:daemon:/sbin:/bin/sh adm:x:3:4:adm:/var/adm:/bin/sh lp:x:4:7:lp:/var/spool/lpd:/bin/sh sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/bin/sh news:x:9:13:news:/var/spool/news:/bin/sh uucp:x:10:14:uucp:/var/spool/uucp:/bin/sh operator:x:11:0:operator:/var:/bin/sh games:x:12:100:games:/usr/games:/bin/sh nobody:x:65534:65534:Nobody:/:/bin/sh messagebus:x:499:499:system user for dbus:/:/sbin/nologin abrt:x:498:498:system user for abrt:/etc/abrt:/sbin/nologin avahi:x:497:497:system user for avahi:/var/avahi:/bin/false avahi-autoipd:x:496:496:system user for avahi:/var/avahi:/bin/false rtkit:x:495:495:system user for rtkit:/proc:/sbin/nologin rpm:x:494:494:system user for rpm:/var/lib/rpm:/bin/false rpc:x:493:493:system user for rpcbind:/var/lib/rpcbind:/sbin/nologin xguest:x:61000:61000:Guest Account:/home/xguest:/bin/rbash vcsa:x:69:492:virtual console memory owner:/dev:/sbin/nologin mysql:x:492:491:system user for mysql:/var/lib/mysql:/bin/bash apache:x:491:490:system user for apache-conf:/var/www:/bin/sh rpcuser:x:490:488:system user for nfs-utils:/var/lib/nfs:/bin/false sshd:x:489:487:system user for openssh:/var/empty:/bin/true memcached:x:488:486:system user for memcached:/dev/null:/bin/false ftp:x:487:417:system user for proftpd:/var/ftp:/bin/false postgres:x:486:416:system user for postgresql8.4:/var/lib/pgsql:/bin/bash tz:x:500:500::/home/tz:/bin/zsh svn:x:485:485:system user for subversion:/home/svn:/bin/false saned:x:484:484:system user for sane:/etc/sane.d:/bin/false 1 ай-яй-яй, а теперь пользователь увидел содержимое файла, совсем для него не предназначенное

Вообще, если данные, введённые в программу пользователем, имеют шанс оказаться в параметре, передаваемом в eval, программисту стоит хорошо подумать, особенно если нет уверенности в добросовестности пользователя. А такой уверенности, как правило, нет, если программа предназначена для удалённого запуска, например, вызывается веб-сервером в ответ на запрос удалённого, неизвестного, возможно недобросовестного пользователя.

Информатика-54© А. Н. Швец