Статьи Королевства Дельфи

       

Реализация и использование


Обработка текста реализована в модуле tem.pm. Процедура tem() получает три параметра: temname, outname и params. Первые два задают, соответственно имя файла шаблона и имя выходного генерируемого файла. Оба они могут быть неопределённы (undef). В этом случае случае будут использованы стандартный вводной поток (STDIN) и стандартный выводной поток соответственно. Третьим параметром передаётся в текстовом виде список параметров шаблона. Всю работу по замене вхождений параметров шаблона в текст их значениями выполняет процедура tem_process(). Хендлы файлов шаблона и генерируемого файла предаются ей параметрами, а наименования параметров и соответствующих им ключей - через переменную модуля хеш-таблицу params, в виде ключей хеша и их значений соответственно. Она обрабатывет текстовой файл построчно, и производит поочерёдно замену всех вхождений параметров значениями в тексте каждой строки. Сформированая при замене строка выводится как строка сгенерированного файла. Ниже приведён текст модуля tem.pm:

use File::Basename; my %params; sub tem_process { my $fin = shift; my $fout = shift; while () { chomp($l = $_); foreach (keys(%params)) { my $ppp = qr/\/; my $val = $params{$_}; $l =~ s/$ppp/$val/g; } print $fout $l,"\n"; } } sub tem { $temname = shift; $outname = shift; $params = shift; if ($temname) { open FIN,$temname die "can't open TEM\n"; } else { FIN = STDIN; } if ($outname) { open FOUT,'>',$outname die "can't open tem OUT\n"; } else { FOUT = STDOUT; } %params = eval '('.$params.')'; $params{"ModName"} = basename($outname,".pas",".PAS"); # а кроме pas ? tem_process(FIN,FOUT); close(FIN) if ($temname); close(FOUT) if ($outname); } 1; __END__

Просмотр исходных файлов и выполнение запросов на инстанциацию (генерацию экземляра исходного модуля по шаблону) осуществляется скриптом temss.pl (ss - это сокращение от "scan sources" - сканирование исходных). Пояснений здесь требует, пожалуй только шаблон поиска запросов: "/!TEM!(\s|\n)*(\S+)(\s|\n)*(\S+)(\s|\n)*((.|\n)*?)(\s|\n)*!MET!/" Он соответствует текстовым последовательностям, заключенным между "!TEM!" и "!MET!" (расположенных на отдельных строках), состоящим из двух строк с ненулевыми непробельными последовательностями, и любого количества строк с последовательностями любых символов. Значимые строки запроса могут перемежаться любым количеством пустых строк. Текстовой файл загружается в память целиком (обычная практика в Perl, он это умеет делать очень быстро) и по нему осуществляется глобальный поиск с приведённым выше шаблоном с последующим выполнением обнаруженных запросов. Ниже приведён текст скрипта:


temss. pl require Tem; sub ss { $src = shift; open FH,$src; my $l = join('',); close(FH); while ($l =~ /!TEM!(\s|\n)*(\S+)(\s|\n)*(\S+)(\s|\n)*((.|\n)*?)(\s|\n)*!MET!/g) { my $temname = $2; my $modname = $4; my $params = $6; print "generate $modname by $temname with\n>>\n"; tem($temname,$modname,$params); } } while () { ss($_); }

Скрипт запускается в директории с исходными модулями, использующими шаблоны. Он сканирует все файлы с расширениями ".pas" и ".dpr" и генерирует тексты исходных модулей по запросам. Если наличествует более одного уровня вложения шаблонов (т.е. одни шаблоны используют другие), то скрипт должен быть запущен соответствующее (количеству уровней вложения) количество раз.
Шаблоны оформляются в виде файлов с расширением ".tem". На текстовое содержание шаблона никаких ограничений не накладывается. Текст шаблона может содержать в себе последовательности вида "", где "ИМЯ_ПАРАМЕТРА" заменяется именем конкретного параметра шаблона (например "<ModName>"). Ниже приведён пример шаблона модуля с функциями Min, и Max (которые кажется первыми были реализованы в виде шаблонов в C++):
MinMax.tem unit <ModName>; interface function Min<Type>(X,Y: <Type>): <Type>; function Max<Type>(X,Y: <Type>): <Type>; implementation function Min<Type>; begin if X < Y then Result := X else Result := Y; end; function Max<Type>; begin if X > Y then Result := X else Result := Y; end; end.

Запрос на инстанциацию шаблона оформляется в тексте исходного модуля, использующего шаблон следующим образом: ... {!TEM! ИМЯ_ФАЙЛА_ШАБЛОНА ИМЯ_ГЕНЕРИРУЕМОГО_МОДУЛЯ ИМЯ_ПАРАМЕТРА1=>ЗНАЧЕНИЕ_ПАРАМЕТРА1, ИМЯ_ПАРАМЕТРА2=>ЗНАЧЕНИЕ_ПАРАМЕТРА2, ... !MET!} ... Имеется один дополнительный параметр - ModName (имя модуля), значение которого определяется именем генерируемого модуля (с отбрасыванием расширения). Приведённый выше шаблон может быть использован следующим образом: ... {!TEM! MinMax.tem MinMaxInt.pas Type=>Integer !MET!} ... В результате выполнения такого запроса получим приведённый ниже модуль:


MinMaxInt. pas unit MinMaxInt; interface function MinInteger(X,Y: Integer): Integer; function MaxInteger(X,Y: Integer): Integer; implementation function MinInteger; begin if X < Y then Result := X else Result := Y; end; function MaxInteger; begin if X > Y then Result := X else Result := Y; end; end.

Важная особенность параметризации шаблонов: при инстанциации заменяются значениями вхождения только тех параметров, которые были указаны в запросе ! Эта свойство делает возможной использование техники каскадной генерации шаблонов - то есть генерацию шаблонов по шаблонам с последовательным уточнением параметров. Эта техника на практике пока не опробована.
Содержание раздела