Макропроцессор M4

Макропроцессор m4, разработанный в 1977 году, легендарными программистами - Брайаном Керниганом (Brian Kernighan) и Денисом Ричи (Dennis Ritchie) предназначен для макрогенерации на предварительном проходе в различных языках. Макрогенерация означает копирование входного символьного потока в выходной, с подстановкой макросов, по мере их появления. Макросы могут быть встроенными или определенными пользователями, и принимать произвольное число аргументов. Имеется множество встроенных функций для включения файлов, запуска внешних команд, выполнения целочисленной арифметики, манипуляции строками.

m4 может быть полезен везде:

программирование
стандартные препроцессоры большинства языков программирования (если таковые есть), обычно сильно слабее, разрешая только простейшие подстановки (исключение — шаблонный препроцессор компилятора C++, но он не является «более мощным», он и m4 просто не сравнимы). m4 даст вам возможность писать компактный и reusable код, там, где это не позволяют средства языка — например, у вас будет порождаемый в момент компиляции 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},{$}4)_n($2){}popdef({_n})})dnl

Внимательно присмотритесь к телу этого макроса. Это хороший пример как можно играться с раскрытием экранирующих кавычек-скобок. Внутри данного макроса определяется «внутренний» макрос _n возвращающий один из своих параметров (номер параметра определяется первым параметром исходного макроса), затем этот макрос вызывается, причем на вход ему подается список — второй параметр исходного макроса. Таким образом подставляется требуемый элемент списка. Затем определение макроса _n уничтожается. По рефлексируйте над этим макросом, представляйте, как происходят подстановки. Вспомним, что при каждой подстановке часть выражения не «защищенного» экранирующими скобками раскрывается, а в части защищенном этими скобками крайние скобки «сьедаются».

   Nth(2,{parameter_field_list})
   -----------
   "ID_OBJECT"

Процесс подстановки:

  1. Вызван макрос Nth. Первый параметр — 2, второй параметр — parameter_field_list (уже без экранирующих скобок).
  2. определяется макрос _n с телом $2.
  3. вызывается макрос _n с набором аргументов «ID_CONFIG»,"ID_OBJECT","MONIKER","NAME","VALUE","COMMENTS","SYS_LEVEL","KIND" (так как при вызове произошла подстановка макроса parameter_field_list)
  4. происходит подстановка макроса _n, при которой подставляется второй аргумент из переданного набора — «ID_OBJECT».
  5. уничтожается определение макроса _n.

Убедитесь, что в описанном процессе все понятно — далее комментарии будут менее подробные.

Следующее, что необходимо для эффективной кодо- или просто тексто- генерации — это итерации. Их (через рекурсию) реализует следующие макросы:

define({forloop}, { pushdef({8},$2) $4{}ifelse($2, $3, , {
    pushdef({9_next}, incr($2))forloop({9}, {9_next}, {$3}, {$4})popdef({9_next})})popdef({9})})

Формально синтаксис вызова этого макроса следующий:

 forloop(имя переменной, стартовое значение, конечное значение, тело цикла)

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

define({$size},{$#})
define({Size}, {ifelse($#,1,{indir({$size},12)})})

Итак, используя перечисленные «базовые» макросы, начнем разрабатывать прикладные: введем макрос для добавления префикса к элементам списка, являющимися колонками (заключенным в кавычки).

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-конструкцию: <code-sql>

UPDATE t_parameter 
   SET update_set2({parameter_field_list},{add_prefix_to_list({parameter_field_list},{:new.})})
 WHERE  id_parameter=123;

</code-sql> после препроцессирования получим: <code-sql> 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; </code-sql>

На этом начальное введение в 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.

СсылкиПравить

Оригинальная документация на английском языке находится здесь.

Базовый справочник и самоучитель (на русском языке), написанный разработчиками компании, находится [mk:@MSITStore:I:\docs\html\lib-book-tech\lib-book-tech.chm::/lib-m4.htm здесь].
По крайней мере часть этого текста взята с ресурса http://lib.custis.ru/ под лицензией GDFL.Список авторов доступен на этом ресурсе в статье под тем же названием.