m4
Макропроцессор m4, разработанный в 1977 году, легендарными программистами - Брайаном Керниганом (Brian Kernighan) и Денисом Ричи (Dennis Ritchie) предназначен для макрогенерации на предварительном проходе в различных языках. Макрогенерация означает копирование входного символьного потока в выходной, с подстановкой макросов, по мере их появления. Макросы могут быть встроенными или определенными пользователями, и принимать произвольное число аргументов. Имеется множество встроенных функций для включения файлов, запуска внешних команд, выполнения целочисленной арифметики, манипуляции строками.
m4 может быть полезен везде:
- программирование
- стандартные препроцессоры большинства языков программирования (если таковые есть), обычно сильно слабее, разрешая только простейшие подстановки (исключение — шаблонный препроцессор компилятора C++, но он не является «более мощным», он и m4 просто не сравнимы). m4 даст вам возможность писать компактный и пригодный для повторного использования код, там, где это не позволяют средства языка — например, у вас будет порождаемый в момент компиляции SQL (вместо динамически генерируемого при исполнении) и даже элементы аспектного программирования — автоматическое порождения блоков кода обеспечивающих единую функциональность в различных компонентах программной системы (например, автоматическое порождение триггеров, обработка ошибок, отладочные проверки и т. п.).
- документирование
- В использовании m4 гораздо более понятен и удобен, чем например, препроцессор от TeX, и вы можете использовать для порождения компактной, гибкой документации при использовании практически любой технологии документирования, основанной на обработке плоских текстов: TeX, LaTeX, Lout, SGML.
- администрирование
- Облегчает написание множества конфигурационных файлов, делая возможность удобно и «прозрачно» работать с «допотопными» форматами, или для гибкого внесения изменений в множество настроек различных сервисов, находящихся в разных файлах (см. например autoconf).
Содержание
Обучение
Разумно сначала использовать m4 для решения простых задач, и лишь затем, переходить к все более и более сложным, походу дела изучая, как писать сложные блоки макросов.
Далее, мы приведем описание макропроцессора m4, включая формат вызова, синтаксис использования, встроенные макрокоманды.
Мы будем описывать версию GNU m4, отличающаяся от стандартного макропроцессора m4, входящего в поставку большинства UNIX-систем, наличием большого числа функциональных расширений, открытостью и переносимостью кода (доступен под любые платформы).
Основные принципы препроцессирования
В данной главе описаны основные принципы препроцессирования m4. Также описаны базовые встроенные макросы.
Работа препроцессора m4 состоит в поиске в текстах входных файлов макровызовов с целью их подстановки. Макровызовы имеют следующий вид:
имя(арг1, арг2, ... аргn)
Левая скобка должна следовать непосредственно за именем макроса. Если за именем определенного макроса не следует «(», то полагается, что макрос вызван без аргументов. Имя макроса может состоять из букв, цифр и символов подчеркивания, причем первым символом не может быть цифра.
На самом деле можно именовать макросы и с нарушением этих правил, но тогда придется вызывать эти макросы с помощью встроенного макроса indir.
В процессе сбора аргументов m4 игнорирует не заключенные в кавычки начальные пробелы, табуляции и символы перевода строки. Для того, чтобы запретить интерпретацию цепочки символов, их заключают между правой и левой одинарными кавычками:
`эта цепочка не интерпретируется'
Значение цепочки символов, заключенной в кавычки, равно самой этой цепочке без внешних кавычек.
Заметим, что символы используемые для обозначения внешних кавычек, можно менять с помощью встроенного макроса changequote. Зачастую это очень удобно по соображениям либо эстетического, либо синтаксического (конфликт с синтаксисом «постпроцессирующего» компилятора) характера.
Когда имя макроса распознано, его аргументы собираются путем поиска парной правой скобки. Если аргументов оказалось меньше, чем используется в определении макроса, то последним аргументам будут даны пустые значения. В процессе сбора аргументов продолжается макрообработка и все не взятые в кавычки запятые и правые скобки, которые образовались в результате вложенных макровызовов, действуют так же, как будто они встретились в исходном тексте. После сбора аргументов значение макроса сканируется повторно, так как если бы оно было в исходном тексте.
m4 предоставляет следующие встроенные макросы, которые могут быть переопределены, но в этом случае их первоначальный смысл теряется. Значения встроенных макросов, если не оговорено противное, равны пустой цепочке.
- define
- второй аргумент становится значением макроса с именем, равным первому аргументу. Если в значение макроса входят пары символов $n, где n — цифра, то при вызове этого макроса они заменяются на n-ый аргумент. Нулевым аргументом является имя макроса; пропущенные аргументы заменяются на пустые цепочки; $# заменяется количеством аргументов; $* заменяется на список всех аргументов, разделенных запятыми; $@ заменяется тоже на список аргументов, но каждый аргумент заключен в кавычки, являющиеся текущими;
- undefine
- удаляет определения макросов, имена которых заданы в качестве аргументов;
- defn
- возвращает заключенные в кавычки определения своих аргументов. Это полезно для переименования макросов, особенно встроенных;
- pushdef
- действует аналогично define, но сохраняет все предыдущие определения;
- popdef
- удаляет текущие определения своих аргументов и восстанавливает предыдущие, если они есть;
- ifdef
- если первый аргумент определен, то значение будет равно второму аргументу, в противном случае третьему. Если третьего аргумента нет, то значение будет пустым;
- shift
- возвращает все свои аргументы кроме первого, заключив их в кавычки и отделив друг от друга запятыми;
- changequote
- первый и второй аргументы становятся новыми символами-кавычками вместо ` и '. Эти аргументы могут содержать до 5 символов. changequote без аргументов возвращает первоначальные значения (то есть `');
- changecom
- Изменяет левый и правый маркеры комментария, которые первоначально равны # и символу перевода строки. Если нет аргументов, то механизм комментариев полностью подавляется. Если задан один аргумент, то он становится левым маркером комментариев, а правым маркером становится перевод строки. Если заданы два аргумента, то они становятся левым и правым маркерами. Маркеры комментариев могут содержать до 5 символов;
- divert
- m4 поддерживает 10 выходных потоков, под номерами 0-9. Окончательный результат получается конкатенацией всех этих потоков в порядке возрастания номеров; первоначально текущим потоком является нулевой. Макрос divert меняет выходной поток на поток с номером, заданным аргументом (в виде цепочки десятичных цифр). Вывод, направленный во все потоки кроме 0-9, теряется.
- undivert
- Вызывает немедленный вывод текста из потоков, номера которых указаны как аргументы, или из всех потоков, если аргументы не указаны. Выведенные потоки очищаются. Текст может быть выведен в любой поток.
- dnl
- Читает и удаляет символы до следующего символа перевода строки включительно.
- ifelse
- Имеет 3 или более аргументов. Если первый аргумент совпадает как цепочка символов со вторым, то результатом будет третий аргумент. Если нет, то, если аргументов более чем 4, то процесс повторяется с аргументами под номером 4, 5, 6 и 7 (4-ый и 5-ый аргументы сравниваются и т. д.). Если же аргументов не более 4, то результатом будет 4-ый аргумент или пустая цепочка при его отсутствии.
- incr
- Возвращает значение своего аргумента, увеличенное на 1. Аргумент, который должен быть цепочкой цифр, интерпретируется как десятичное число.
- decr
- Возвращает значение своего аргумента, уменьшенное на 1.
- eval
- Вычисляет свой аргумент как арифметическое выражение, используя 32-битную арифметику. Допустимые операции включают +, -, *, /, %; побитные операции &, |, ^, и ~; операции отношения; скобки. Восьмеричные и шестнадцатеричные числа могут быть заданы как в языке C. Второй аргумент указывает систему счисления для результата; по умолчанию результат десятичный. Если задан третий аргумент, то он определяет минимальное число цифр в результате.
- len
- Возвращает количество символов в своем аргументе.
- index
- Возвращает позицию в первом аргументе, с которой начинается второй аргумент. Позиции нумеруются с 0. В случае неудачи поиска возвращается ?1.
- substr
- Возвращает подцепочку первого аргумента. Второй аргумент задает номер начального символа подцепочки (считая от 0), третий аргумент указывает длину. Если третий аргумент опущен, то подцепочка продолжается до конца первого аргумента. Если вторым и третьим аргументами задана подцепочка, выходящая за пределы данной цепочки, то результатом будет их пересечение.
- translit
- Заменяет в первом аргументе символы, которые входят во второй аргумент, на символы, стоящие в третьем аргументе на тех же позициях. Не допустимы никакие сокращения.
- include
- Возвращает содержимое файла с именем, заданным аргументом.
- sinclude
- То же самое, что и include, за исключением того, что не будет никакой реакции, если файл недоступен.
- syscmd
- Выполняет команду системы UNIX, заданную первым аргументом. Никакого значения не возвращается.
- sysval
- Код завершения последнего вызова syscmd.
- maketemp
- В подцепочку аргумента, имеющую вид XXXXX, вписывается идентификатор текущего процесса.
- m4exit
- Вызывает немедленный выход из m4. Первый аргумент, если он есть, будет кодом завершения; подразумевается 0.
- m4wrap
- Помещает свой первый аргумент в конец исходного текста, например:
m4wrap(`cleanup()')
- Эффективен только один вызов m4wrap, из нескольких вызовов действие окажет только последний.
- errprint
- Выдает свой аргумент в стандартный протокол.
- dumpdef
- Выдает текущие имена и определения для указанных макросов, или для всех макросов, если нет аргументов.
- traceon
- Если аргументы не заданы, включает трассировку всех макросов (в том числе и встроенных); иначе включает трассировку только указанных макросов.
- traceoff
- Выключает трассировку глобально или для указанных макросов специально. Если трассировка некоторого макроса была включена вызовом traceon с аргументами, то и выключить его трассировку можно только вызовом traceoff с именем этого макроса в качестве аргумента, а не глобальным traceoff без аргументов.
- indir
- Предназначен для вызова макросов с нестандартными именами. первый аргумент — имя макроса, остальные — аргументы вызываемого макроса. Стандартный пример:
define(`$internal$macro', `Internal macro (name `$0')') => $internal$macro =>$internal$macro indir(`$internal$macro') =>Internal macro (name $internal$macro)
Формат и опции командной строки
Синтаксис вызова:
m4 [опции...] [макроопределения...] [входные файлы...]
Все файлы-аргументы обрабатываются по очереди. Если файлы не указаны, или в качестве имени файла задан «-», то читается стандартный ввод. Обработанный текст записывается на стандартный вывод.
Все опции начинаются с «-», если используется короткая (как правило однобуквенная) запись опции, или «--» если используется длинная запись. Заметим, что необязательно писать полностью длинное название опции, достаточно написать однозначно определяющий ее префикс.
Ниже перечислены наиболее часто используемые опции:
--version
печатает версию программы и немедленно выходит;
-G, --traditional
подавляет обработку всех дополнений по сравнению со стандартом System V;
-E, --fatal-warnings
прекратить обработку после первой ошибки;
-Iкаталог, --include=каталог
указывает каталог для поиска включаемых файлов, ненайденных в текущем каталоге;
-s, --synclines
генерация синхронизирующих директив вида #line linenum «filename»;
-Dимя, -Dимя=значение, --define=имя, --define=имя=значение
определение макроса до начала компиляции; если значение не задано, оно считается пустой строкой;
-Uимя, --undefine=имя
удаление определения ранее предопределенного в командной строке имени.
Уроки-примеры генерации кода
Приведем начальное обучение базовым навыкам использования препроцессора m4 для генерации кода 3GL и 4GL языков путем разбора примеров.
В приведенных в разделе примерах символы используемые для обозначения внешних кавычек, будут заменены на фигурные скобки. Это делается с помощью встроенного макроса changequote:
changequote({,})
Подразумевается, что перед чтением данной главы читатель уже ознакомился с разделом #Основные принципы препроцессирования.
m4 предоставляет удобные возможности для работы со списками. Использование списков в свою очередь, позволяет легко масштабировать программный код.
Список определяется, как обычный макрос. Например, это список колонок в таблице.
define({parameter_field_list}, {"ID_CONFIG","ID_OBJECT","MONIKER","NAME","VALUE","COMMENTS","SYS_LEVEL","KIND"})
С помощью приведенного ниже макроса, мы будем иметь возможность брать произвольный элемент ранее определенного списка.
define({Nth}, {pushdef({_n},{$}$1)_n($2){}popdef({_n})})
Внимательно присмотритесь к телу этого макроса. Это хороший пример как можно играться с раскрытием экранирующих кавычек-скобок. Внутри данного макроса определяется «внутренний» макрос _n возвращающий один из своих параметров (номер параметра определяется первым параметром исходного макроса), затем этот макрос вызывается, причем на вход ему подается список — второй параметр исходного макроса. Таким образом подставляется требуемый элемент списка. Затем определение макроса _n уничтожается. По рефлексируйте над этим макросом, представляйте, как происходят подстановки. Вспомним, что при каждой подстановке часть выражения не «защищенного» экранирующими скобками раскрывается, а в части защищенной этими скобками крайние скобки «съедаются».
Nth(2,{parameter_field_list}) ----------- "ID_OBJECT"
Процесс подстановки:
- Вызван макрос Nth. Первый параметр — 2, второй параметр — parameter_field_list (уже без экранирующих скобок).
- определяется макрос _n с телом $2.
- вызывается макрос _n с набором аргументов «ID_CONFIG»,"ID_OBJECT","MONIKER","NAME","VALUE","COMMENTS","SYS_LEVEL","KIND" (так как при вызове произошла подстановка макроса parameter_field_list)
- происходит подстановка макроса _n, при которой подставляется второй аргумент из переданного набора — «ID_OBJECT».
- уничтожается определение макроса _n.
Убедитесь, что в описанном процессе все понятно — далее комментарии будут менее подробные.
Следующее, что необходимо для эффективной кодо- или просто тексто- генерации — это итерации. Их (через рекурсию) реализует следующие макросы:
define({forloop},{pushdef({$1},{$2})_forloop({$1},{$2},{$3},{$4})popdef({$1})}) define({_forloop},{$4{}ifelse($1,{$3}, ,{define({$1},incr($1))_forloop({$1},{$2},{$3},{$4})})})
Формально синтаксис вызова этого макроса следующий:
forloop(имя переменной, стартовое значение, конечное значение, тело цикла)
Введя еще макрос для получения длины списка, мы сразу получим возможность реализовывать мощные, масштабируемые конструкции.
define({Size},{ifelse($#,1,{indir({$$size},$1)})}) define({$$size},{$#})
Итак, используя перечисленные «базовые» макросы, начнем разрабатывать прикладные: введем макрос для добавления префикса к элементам списка, являющимися колонками (заключенным в кавычки).
define({add_prefix_to_list},{ifelse($#,2,{forloop({j}, 1, Size({13}),{ifelse(j(),1,,{,})patsubst(Nth({j},{13}),{^[^"]*"},$2")})})})
Введем макрос, который для элементов списка, являющихся колонками формируется синтаксическая конструкция вида
колонка_из_списка1_i = колонка_из_списка2_i, ....
где i — порядковый номер колонки в списке (UPDATE SET CLAUSE).
define({update_set2},{forloop({j},1,Size({15}),{ifelse(eval(index(Nth({j},{15}),{"})>=0),1,{ ifdef({ok},{,})}Nth({j},{16}){ifdef({ok},,{pushdef({ok},1)})}=Nth({j},{$2}))})popdef({ok})})
Итак, используя вышеприведенное определение для parameter_field_list, мы можем легко написать следующую «пре»-SQL-конструкцию:
UPDATE t_parameter SET update_set2({parameter_field_list},{add_prefix_to_list({parameter_field_list},{:NEW.})}) WHERE id_parameter=123;
после препроцессирования получим:
UPDATE t_parameter SET «ID_CONFIG»=:NEW."ID_CONFIG" ,"ID_OBJECT"=:NEW."ID_OBJECT" ,"MONIKER"=:NEW."MONIKER" ,"NAME"=:NEW."NAME" ,"VALUE"=:NEW."VALUE" ,"COMMENTS"=:NEW."COMMENTS" ,"SYS_LEVEL"=:NEW."SYS_LEVEL" ,"KIND"=:NEW."KIND" WHERE id_parameter=123;
На этом начальное введение в m4 можно считать завершенным. Опыт написания объемных программных текстов с помощью m4 показал, что для этого достаточно знания основных встроенных макросов и использования описанных выше стандартных макросов для работы со списками и для реализации итераций.
Тогда можно писать программные тексты произвольной масштабируемости — вводить с помощью списков декларативное описание структур и вперед!
Не пытайтесь использовать M4 как «серебрянную пулю» для всех IT-задач — не пытайтесь заменить им везде XML, языки обработки текстов вида Python, Perl — будет плохо. Причем, в ограничения этого препроцессора вы упретесь не сразу. Всегда помните заповеди, приведенные в начале стандартной документации по M4:
Some people find m4 to be fairly addictive. They first use m4 for simple problems, then take bigger and bigger challenges, learning how to write complex sets of m4 macros along the way. Once really addicted, users pursue writing of sophisticated m4 applications even to solve simple problems, devoting more time debugging their m4 scripts than doing real work. Beware that m4 may be dangerous for the health of compulsive programmers.
Ссылки
Оригинальная документация на английском языке находится здесь.
Любые правки этой статьи будут перезаписаны при следующем сеансе репликации. Если у вас есть серьезное замечание по тексту статьи, запишите его в раздел «discussion».
Репликация: База Знаний «Заказных Информ Систем» → «M4»