GNU m4, версия 1.4
Мощный макропроцессор
Редакция 1.4, Ноябрь 1994
Ren'e SeindalРусский перевод - (C) А.Гавва
г.Львов январь 2003г.
Содержание
- Введение и предварительные замечания
- Лексические и синтаксические соглашения
- Как вызывать макросы
- Как определять новые макросы
- Условия, циклы и рекурсия
- Как отлаживать макросы и ввод
- Управление вводом
- Включение файлов
- Перенаправление (diverting) и отмена перенаправления (undiverting) вывода
- Макросы для обработки текста
- Арифметические макросы
- Выполнение команд UNIX
- Различные встроенные макросы
- Быстрая загрузка "замороженных" состояний
- Совместимость с другими версиями
m4
Copyright (C) 1989, 90, 91, 92, 93, 94 Free Software Foundation, Inc.
Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies.
Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one.
Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions, except that this permission notice may be stated in a translation approved by the Foundation.
Введение и предварительные замечания
Эта глава - вводная.
Она рассказывает о том, что такое GNU m4
,
как m4
появился,
как читать и использовать эту документацию,
как запусть программу m4
на выполнение
и как сообщать об обнаруженных в этой программе ошибках.
Таким образом, эта глава содержит некоторые подсказки
которые будут полезны при чтении остального материала
этого учебника.
Остальные главы учебника предоставляют детальные сведения
об особенностях языка m4
.
Введение в m4
m4
- это макропроцессор
который осуществляет копирование данных с его входа
на выход, расширяя макросы по мере их поступления.
Макросы бывают строенными или определяемыми пользователем,
и могут содержать любое количество аргументов.
Кроме выполнения макрорасширений (макроподстановок),
m4
имеет встроенные функции для включения именованых файлов,
запуска команд UNIX, выполнения целочисленной арифметики,
различного манипулирования текстом,
осуществления рекурсии, и т.д.
m4
может быть использован как препроцессор (front-end) какого-либо компилятора,
или как самостоятельный макропроцессор.
Макропроцессор m4
широко используется во всех системах UNIX.
Обычно, о его наличии беспокоится достаточно не большой процент пользователей.
Однако, есть и те кто используют его часто.
Растущая популярность пакета GNU Autoconf, который требует наличия GNU m4
для генерации скриптов `configure', стимулирует многих пользователей
устанавливать m4
даже если они никогда не будут его реально использовать
для программирования.
GNU m4
в большинстве случаев совместим с версией
для системы System V Release 3, исключая несколько
небольших различий.
Более полная информация содержится в секции
Совместимость с другими версиями m4
.
Некоторые люди привыкают к использованию m4
.
Сначала они используют m4
для решения простых задач,
потом решаемы задачи становятся более сложными,
требуя значительно больше времени на отладку скриптов m4
чем на выполнение непосредственной работы.
Учтите, что m4
может стать опасен для здоровья некоторых
заядлых программистов.
Исторические сведения
Упоминаемые здесь исторические сведения очень не полные и абсолютно не авторитарны. Для более полного написания этой секции, необходима помощь осведомленных пользователей.
Важным прародителем m4
был GPM
.
См. C. Stratchey: "A General Purpose Macro generator", Computer Journal
8,3 (1965), pp. 225 ff..
Кроме того, GPM
описан в классической книге
David Gries "Compiler Construction for Digital Computers".
В то время как GPM
был чистым, m4
был
задуман как средство для работы с истинной запутанностью реальной жизни:
макросы могли быть распознаны как предопределенные,
отброс пробелов или символов завершения строки был сделан более легким,
множество конструкций было встроено вместо использования производных, и т.д.
В оригинале, m4
был разработан как основа препроцессора для Rational FORTRAN,
таким образом, ratfor
является эквивалентом cpp
.
Запуск m4
Формат командной строки для запуска m4
, следующий:
m4
[option...] [macro-definitions...] [input-file...]
Все опции начинаются с `-', или, при использовании опций с длинными именами,
с `--'.
Длинные имена опций могут быть записаны не полностью,
использование недвусмысленных префиксов достаточно.
m4
понимает следующие опции:
--version
-
Распечатывает в стандартный вывод номер версии программы,
после чего работа
m4
завершается немедленно, без чтения каких-либо входных файлов input-files. --help
-
Распечатывает в стандартный вывод общую подсказку,
после чего работа
m4
завершается немедленно, без чтения каких-либо входных файлов input-files. -G
--traditional
-
Подавить все расширения, в сравнении с версией System V, сделанные в этой реализации.
Для более полной информации
см. секцию Совместимость с другими версиями
m4
. -E
--fatal-warnings
-
Остановить выполнение и выйти из
m4
при первой же выдаче предупреждающего сообщения, рассматривая выдачу предупреждающего сообщения как фатальное. -dflags
--debug=flags
- Установить уровень отладки в соответствии с флагами flags. Уровень отладки управляет форматом и количеством информации предоставляемой функциями отладки. Для более полной информации о форматах и смысловому значению flags см. секцию Управление отладочным выводом.
-lnum
--arglength=num
- Ограничить размер вывода, генерируемого трассировкой макросов. Для более полной информации см. секцию Управление отладочным выводом.
-ofile
--error-output=file
- Перенаправить вывод отладки и трассировки в указанный файл. Вывод сообщений об ошибках на стандартное устройство вывода ошибок сохраняется. Для более полной информации см. секцию Сохранение отладочного вывода.
-Idir
--include=dir
-
Указывает
m4
необходимоть поиска включаемых файлов в каталоге dir когда включаемые файлы не найдены в текущем каталоге. Для более полной информации см. секцию Поиск включаемых файлов. -e
--interactive
-
Сделать этот вызов
m4
интерактивным. Это подразумевает, что весь вывод не будет буферизироваться, и прерывания будут игнорироваться. -s
--synclines
-
Генерировать строки синхронизации для использования препроцессора C или подобных средств.
Это полезно, например, когда
m4
используется как препроцессор для какго-либо компилятора. Имена исходных файлов и информация о нумерации строк передаются директивами вида `#line linenum "filename"', которые необходимым образом вставляются в середину ввода. Подобные директивы подразумевают, что следующие строки порождены или расширены из содержимого входного файла filename в строке linenum. Часть `"filename"' часто бывает опущена когда имя файла не было изменено предыдущей директивой. Указание директив синхронизации всегда занимает самостоятельную строку. При возникновении расхождений в синхронизации в середине выводимой строки, ассоциируемая директива синхронизации задерживается до начала следующей сгенерированной строки. -P
--prefix-builtins
- Выполняет внутреннюю модификацию всех встроенных макросов, так чтобы они начинались с префикса `m4_'. Например, при использовании этой опции, необходимо писать `m4_define' вместо `define', и SAMP>`m4___file__' вместо `__file__'.
-WREGEXP
--word-regexp=REGEXP
-
Использовать альтернативный синтаксис для имен макросов.
Эта экспериментальная опция может отсутствовать в некоторых реализациях GNU
m4
. (Для более полной информации см. секцию Изменение лексической структуры слов). -Hn
--hashsize=n
- Устанавливает длину внутренней хэш таблицы поиска символов в n. Число должно быть простым. Используемое значение по умолчанию 509. Без описания обширного количества макросов необходимость в увеличении этого числа не возникает.
-Ln
--nesting-limit=n
-
Искусственно ограничивает вложенность макро-вызовов до n уровней,
останавливая выполнение программы в случае достижения или превышения этого уровня.
Без указания, уровень вложенности макро-вызовов ограничен величиной 250.
Более точный эффект действия этой опции может быть более корректно ассоциирован
с текстуальной вложенностью, а не как динамическая рекурсия.
Это может быть полезно когда подразумевается, что сложные входные данные
m4
генерируются механически. Большинству пользователей никогда не потребуется использование этой опции. Если показанное будет навязываться, то эта опция (которая остается экспериментальной) пожет исчезнуть. Эта опция не имеет возможности прекратить бесконечные циклы пересканирования, пока не будет исчерпано доступное пространство памяти или стека. Удачное использование циклов пересканирования может вызвать сложные и длительные вычисленияm4
с полезными результатами. Установка ограничений в этой области может уменьшить мощностьm4
. Существует множество патологических ситуаций. Пример `define(`a', `a')a' является одним из простейших (Для более полной информации см. секцию Совместимость с другими версиямиm4
). Ожидание чтобы GNUm4
детектировал подобные ситуации может показаться подобным ожиданию чтобы система компиляции детектировала и проверяла наличие бесконечных циклов: в общем это весьма трудная проблема, которая в общем является неразрешимой! -Q
--quiet
--silent
- Подавляет предупреждающие сообщения об отсутствующих или излишних (superflous) аргументах в макро-вызовах.
-B
-S
-T
-
Наличие этих опций представлено только с целью обеспечения совместимости с версией
m4
для System V, однако реально они ничего не делают. -Nn
--diversions=n
-
Наличие этих опций представлено только с целью обеспечения совместимости
с предыдущими версиями
m4
где присутствовало управление количеством возможных отклонений которые согли быть использованы одновременно. Реально, эти опции ничего не делают, поскольку теперь такой лимит больше не фиксируется.
Описание и удаление макро-определения может быть выполнено в командной строке с помощбю использования опций `-D' и `-U'. Это имеет следующий фомат:
-Dname
-Dname=value
--define=name
--define=name=value
- Это помещает name в таблицу символов, до чтения любого входного файла. Если `=value' отсутствует, то значение символа содержит пустую строку. Значение value может быть любой строкой и макро-определение может описывать наличие макро-аргументов, также, как это делается при описании макро-определения на входе (во входном файле).
-Uname
--undefine=name
- Это удаляет любой предопределенный смысл name. Очевидно, что таким образом можно удалять только предопределенные макро-определения.
-tname
--trace=name
- Это вводит name в таблицу символов как неописанное, но трассируемое. В результате, макро-определение будет трассироваться начиная от точки его описания.
-Ffile
--freeze-state file
- Как только заканчивается выполнение, осуществляется запись "замороженного" состояния в указанный файл file (для более полной информации см. секцию Быстрая загрузка "замороженных" состояний).
-Rfile
--reload-state file
- До начала выполнения, восстановить внутреннее состояние из указанного файла "замороженного" состояния file (для более полной информации см. секцию Быстрая загрузка "замороженных" состояний).
Остальныме аргументы командной строки принимаются как имена входных файлов. Если не указано ни одного имени входного файла, то производится чтение устройства стандартного ввода. Принимается имя файла `-', которое подразумевает устройство стандартного ввода.
Чтение входных файлов осуществляется в указанной последовательности. Устройство стандартного ввода может быть прочитано только однократно, таким образом, имя файла `-' может быть указано в командной строке не более одного раза.
Проблемы и ошибки
Если в процессе использования GNU m4
у вас возникли проблемы,
или вам показалось, что вы обнаружили ошибку, пожалуйста сообщите об этом.
Перед тем как сообщать об ошибке, убедитесь в том,
что вы действительно обнаружили реальную ошибку.
Внимательно перечитайте документацию и убедитесь
в том, что вы можете сделать то, что вы пытаетесь.
Если после чтения документации остается не понятным,
можете-ли вы сделать то, что вы пытаетесь сделать,
то сообщите об этом также, поскольку это указывает на наличие ошибки в документации!
Перед тем как сообщать об ошибке или попытке исправить ее самостоятельно,
попытайте изолировать ее в минимальном (по размеру), насколько это возможно,
входном файле который способен воспроизвести проблему.
Затем, пошлите нам входной файл и полученные вами результаты работы m4
.
Также, опишите то что вы ожидали получить как результат работы m4
;
это поможет нам определить содержится-ли проблема в документации.
Как только вы получите точное воспроизведение проблемы,
пошлите через e-mail по адресу (Internet)
`bug-gnu-utils@prep.ai.mit.edu'
или (UUCP) `mit-eddie!prep.ai.mit.edu!bug-gnu-utils'.
При этом, пожалуйста, укажите номер используемой вами версии m4
.
Вы можете узнать эту информацию с помощью команды `m4 --version'.
Сообщения не об ошибках, с предложениями также всегда приветствуются. Если вы имеете вопросы о чем-либо, что плохо описано в документации или затемняет использование средств, пожалуйста,сообщите об этом также.
Использование этого руководства
Это руководство содержит множество примеров ввода и вывода m4
,
для того чтобы четко различать ввод, вывод и сообщения об ошибках m4
используется простая нотация.
Текст примеров также отличается от обычного текста и показан шрифтом
с фиксированной шириной, подобно следующему:
Это пример текста примера!
Для различия ввода от вывода, весь вывод m4
предваряется строкой `=>',
а все сообщения об ошибках - строкой `error-->'.
Таким образом:
пример строки ввода =>строка вывода m4 error-->сообщение об ошибке
Поскольку макро-определения (макросы), предопределенные в m4
, описаны,
прототип вызова макроса будет показан предоставляя информативно-наглядные имена
аргументов, например:
regexp(string, regexp, opt replacement)
В m4
, все аргументы макросов являются строками,
но некоторые имеют специальную интерпретацию, например числа, имена файлов, регулярные выражения
и т.д.
Указвние `opt' перед третьим аргументом, говорит о том, что этот аргумент опционален, и если он отсутствует, то значение его содержимого будет пустой строкой. Указание многоточия (`...') в конце списка аргументов индицирует, что далее может следовать любое число аргументов
Этот документ согласованно пишет и использует builtin
без дефиса, как это требовалось бы при написании английского слова.
Именно таким образом примитив builtin
записывается внутри m4
.
Лексические и синтаксические соглашения
В процессе чтения ввода m4
, он разделяет его в токены (tokens).
Токены могут быть именами, строками в кавычках или любыми одиночными символами,
которые не являются частью какого-либо имени или строки.
Ввод m4
может также содержать комментарии.
Имена
Имя - это любая последовательность букв, цифр и символов подчеркивания _, не начинающаяся с цифры. Если имя имеет макро-определение, то оно будет субъектом для макро-расширения (см. секцию Как вызывать макросы).
Примерами допустимых имен могут служить: `foo', `_tmp', and `name01'.
Строки в кавычках (цитирование)
Строки в кавычках (иначе, цитируемые строки) являются последовательностями символов, заключенных в кавычки ` и ', причем число начальных и завершающих кавычек сбалансировано. Значение строкового токена является текстом у которого один уровень кавычек отброшен. Таким образом:
`'
является пустой строкой, а
``quoted''
является строкой
`quoted'
Символы кавычек могут быть изменены в любой момент,
с помощью использования встроенного макроса changequote
.
Для более полной информации
см. секцию Изменение символов кавычек.
Прочие токены
Любой символ, который не является частью имени или строки в кавычках, является самостоятельным токеном.
Комментарии
Комментарии m4
нормально ограниченны символом `#'
и символом новой строки.
Все символы между символами ограничения комментариев игнорируются,
но содержимое всего коментария (включая символы ограничения комментариев)
передается на вывод, то есть, комментарии не отбрасываются m4
.
Комментарии не могут быть вложенными, таким образом, первый же символ новой строки, после символа `#', завершает комментарий. Комментирующий эффект символа начала комментария может быть подавлен помещением этого символа в кавычки.
В любой момент времени, с помощью встроенного макроса changecom
,
символы ограничения комментариев могут быть изменены на любую строку.
Для более полной информации
см. секцию Изменение ограничителей комментариев.
Как вызывать макросы
Эта глава демонстрирует макро-вызовы, макро-аргументы и то как трактуется макро-расширение.
Вызов макроса
Макро-вызовы имеют одну из форм
name
которая является макро-вызовом без единого аргумента, или
name(arg1, arg2, ..., argn)
которая является макро-вызовом с n аргументами. Макросы могут иметь любое количество аргументов. Все аргументы являются строками, но различные макросы могут интерпретировать свои артументы различными способами.
Открывающие скобки должны непосредственно сопровождаться name, без пробелов между ними. Если это не так, то макрос будет вызван без единого аргумента.
Для макро-вызова, который не должен иметь аргументов, не должны использоваться скобки. Макро-вызов
name()
является вызовом с одним аргументом, которым является пустая строка, а не вызовом без аргументов.
Предотвращение вызова макроса
Одной из инноваций языка m4
, в сравнении с его предшественниками
(подобно Stratchey's GPM
, например),
является возможность распознавания макро-вызовы без использования каких-либо специальных,
предшествующих вызову символов.
Хотя это удобно в общем,
такое свойство может иногда быть источником ложного, нежелательного макро-вызова.
Таким образом, GNU m4
предоставляет некоторые механизмы или приемы
для предотвращения расценивания имен как макро-вызовов.
Прежде всего, многие встроенные макросы не могут быть осмысленно вызваны без аргументов. Для любого из этих макросов, когда за открывающей скобкой непосредственно не следует имя параметра, вызов встроенного макроса не воспринимается. Это позволяет распутать большинство обычных случаев, подобных использованию `include' или `eval'. Позже, в этом документе, предложение "This macro is recognized only when given arguments" ссылается к этому специфическому соглашению.
Существует также опции командной строки
(--prefix-builtins
или -P
)
которые требуют, чтобы все имена встроенных макросов предварялись `m4_'
(иначе, использовали `m4_' как префикс),
с целью правильного распознавания встроенных макросов.
Опция не имеет эффекта для любого макроса, объявленного пользователем.
Например, с этой опцией, можно написать m4_dnl
и даже m4_m4exit
.
Если версия GNU m4
, используемая вами,
содержит прекомпилированное средство changeword
,
то она предлагает большую гибкость в спецификации синтаксиса
как встроенных, так и определяемых пользователем макро-имен.
Для более полной информации об этом экспериментальном средстве
см. секцию Изменение лексической структуры слов.
Естественно, самым простым способом предотвратить интерпретацию какого-либо имени как вызов существующего макроса является помещение этого имени в кавычки. Остальная часть этой секции немного глубже рассматривает то, как помещение в кавычки влияет на вызов макроса, и как помещение в кавычки может быть использовано для предотвращения макро-вызова.
Даже если помещение в кавычки выполняется обычно для всего имени макроса, это может быть сделано только для нескольких символов этого имени макроса. Возможно также заключение в кавычки пустой строки, но это будет работать только внутри имени. Например:
`divert' `d'ivert di`ver't div`'ert
все это дает строку `divert'. В то время как оба варианта:
`'divert divert`'
будет вызывать встроенный макрос divert
.
Вывод оценки макроса всегда пересканируется.
Следующий пример будет вызывать строку `de',
точно также если на ввод m4
будет выдано
`substr(abcde, 3, 2)':
define(`x', `substr(ab') define(`y', `cde, 3, 2)') x`'y
Строки, не помещенные в кавычки, с обоих сторон строк, помещенных в кавычки,
являются субъектами, распознаваемыми как имена макросов.
В следующем примере, помещение в кавычки пустой строки,
позволяет распознать макрос dnl
следующим образом:
define(`macro', `di$1') macro(v)`'dnl
Без использования кавычек, это даст строку `divdnl' сопровождаемую символом конца строки.
Помещение в кавычки может предотвратить распознавание конкатенации макро-расширения с окружающими символами как имени макроса. В этом примере:
define(`macro', `di$1') macro(v)`ert'
ввод будет создавать строку `divert'.
Если убрать кавычки, то вместо этого, будет вызван встроенный макрос divert
.
Макро-аргументы
Когда имя распознано, и оно имеет макро-определение, то оно будет расширено как макрос.
Если имя сопровождается открывающейся скобкой, то, до того как выполняется вызов макроса, осуществляется накопление аргументов. Если указанных аргументов меньше, то отсутствующие аргументы трактуются как пустые строки. Если предоставлено больше аргументов, то лишние аргументы игнорируются.
Обычно m4
генерирует предупреждающие сообщения,
когда встроенные макросы вызываются с не подходящим количеством аргументов,
но предупреждающие сообщения могут быть подавлены
опцией командной строки `-Q'.
Проверка количества аргументов, для определяемых пользователем макросов, отсутствует.
Обычно, макросы расширяются в процессе накопления аргументов, и все запятые, кавычки и скобки, которые могут быть показаны в результирующем расширенном тексте, будут также обеспечивать описание аргументов. Таким образом, если foo расширяется в `, b, c', то макро-вызов:
bar(a foo, d)
является макро-вызовом с четырьмя аргументами, которыми являются `a ', `b', `c' и `d'. Чтобы понять почему первый аргумент содержит пробел, следует иметь в виду, что предшествующие (лидирующие) не помещенные в кавычки пробелы никогда не являются частью аргумента, но последующие (завершающие) пробелы всегда являются частью аргумента.
Помещение макро-аргументов в кавычки
Каждый аргумент не должен иметь предшествующие (лидирующие) пробелы которые не помещены в кавычки. Внутри каждого аргумента, все скобки, которые не помещены в кавычки, должны быть парными. Например, если foo является макросом,
foo(() (`(') `(')
является вызовом макроса с одним аргументом, значением которого будет `() (() ('.
Существует общая практика помещать в кавычки все аргументы макроса, если нет уверенности в том, что необходима возможность расширения аргументов. Таким образом, в показанном выше примере со скобками, правильным способом выполнения этого будет:
foo(`() (() (')
Однако, в некоторых случаях возникает необходимость не заключать в кавычки некоторые аргументы, и в этом нет ничего страшного. Это делает жизнь несколько труднее, если вы не очень внимательны.
Макро-расширение
Когда аргументы (если они есть) для макро-вызова накоплены, то происходит расширение макроса, и текст расширения возвращается обратно на вход (без помещения его в кавычки) и перечитывается. Таким образом, текст расширения одного макро-вызова может привести к вызову множества макросов, если вызовы включены (полностью или частично) в первое расширение макро-вызова.
Рассмотрим очень простой пример. Если foo будет расширен в `bar', а bar расширяется в`Hello world', то ввод
foo
сначала будет расширен в `bar', а затем, перечитан и расширен в `Hello world'.
Как определять новые макросы
Макросы могут быть определены, переопределены и удалны различными способами. Также, существует возможность переопределять любой макрос, без потери его первоначального значения, которое позже может быть восстановлено обратно.
Определение макроса
Обычным способом определения и переопределения макросов
является использование встроенного макроса define
:
define(name [, expansion])
который определяет, что имя name может быть расширено как expansion. Если expansion не указано, то считается, что расширение - пустое.
Встроенный макрос define
имеет пустое (void) расширение.
Следующий пример определяет, что макрос foo расширяется как текст `Hello World.'.
define(`foo', `Hello world.') => foo =>Hello world.
Появление пустой строки в выводе обусловлено тем, что новая строка (newline)
не является частью макро-определения, и, следовательно, она копируется в вывод.
Этого можно избежать путем использования макроса dnl
.
Более полная информация содержится в секции
Удаление пробелов из ввода.
Макрос define
распознается только при наличии параметров.
Аргументы для макроса
Макросы могут иметь параметры.
Аргумент с порядковым номером n обозначается как $n
в тексте расширения,
и заменяется фактическим значением аргумента с порядковым номером n,
во время расширения макроса.
Рассмотрим пример макроса с двумя аргументами.
Этот макрос осуществляет простое изменение порядка следования двух аргументов.
define(`exch', `$2, $1') => exch(arg1, arg2) =>arg2, arg1
Это может быть использовано, например, если вам нравится чтобы порядок следования
аргументов макроса define
был обратным.
define(`exch', `$2, $1') => define(exch(``expansion text'', ``macro'')) => macro =>expansion text
За объяснениями по использовинию двойных кавычек обратитесь к секции Помещение макро-аргументов в кавычки.
GNU m4
допускает, чтобы числа, следующие за `$',
состояли из одной и более цифр, позволяя макросам иметь любое количество аргументов.
Это не поддерживается реализациями m4
для UNIX,
которые способны распознать только одну цифру.
Как специальный случай, нулевой аргумент, $0
, всегда является именем
расширяемого макроса.
define(`test', ``Macro name: $0'') => test =>Macro name: test
Если необходимо, чтобы текст в кавычках появился как часть текста расширения, помните, что кавычки могут быть вложенными в строки заключенные в кавычки. Таким образом, в
define(`foo', `This is macro `foo'.') => foo =>This is macro foo.
указанное в тексте расширения `foo' не расширено, поскольку является строкой помещенной в кавычки, а не именем.
Специальные аргументы для макроса
Существует специальная нотация для определения числа фактически переданных аргументов и для всех фактических аргументов (одновременно).
Число фактических аргументов макро-вызова обозначается в тексте расширения с помощью $#
.
Таким образом, макрос, отображающий число фактически переданных ему аргументов, может иметь вид
define(`nargs', `$#') => nargs =>0 nargs() =>1 nargs(arg1, arg2, arg3) =>3
Для определения, в тексте расширения, всех фактических аргументов (одновременно),
без помещения их в кавычки и разделяя их запятыми, может быть использована нотация $*
.
Например
define(`echo', `$*') => echo(arg1, arg2, arg3 , arg4) =>arg1,arg2,arg3 ,arg4
Часто каждый аргумент должен быть помещен в кавычки, и нотация вида $@
может это обработать.
Это подобно $*
, за исключением того, что в кавычки помещается каждый аргумент.
Простым примером этого является:
define(`echo', `$@') => echo(arg1, arg2, arg3 , arg4) =>arg1,arg2,arg3 ,arg4
А куда делись кавычки?
Естественно, они были "съедены", когда расширенный текст был перечитан m4
.
Для того, чтобы продемонстрировать разницу, попробуйте
define(`echo1', `$*') => define(`echo2', `$@') => define(`foo', `This is macro `foo'.') => echo1(foo) =>This is macro This is macro foo.. echo2(foo) =>This is macro foo.
Посмотрите секцию Трассировка макро-вызовов, если вы этого не поняли.
В тексте расширения, самостоятельный знак `$', который не сопровождается ничем
что понимается m4
, просто копируется в макро-расширение,
также как и любой другой текст.
define(`foo', `$$$ hello $$$') => foo =>$$$ hello $$$
Если необходимо расширить макрос в что-нибудь подобное `$12',
поместите пару кавычек после $
.
Это предохранит m4
от интерпретации знака $
как обрашение к какому-либо аргументу.
Удаление макроса
Любое макро-определение может быть удалено с помощью встроенного макроса undefine
:
undefine(name)
который удаляет макрос с именем name. Макро-имя name должно быть помещено в кавычки, поскольку, в противном случае, оно будет расширено.
Встроенный макрос undefine
имеет пустое (void) расширение.
foo =>foo define(`foo', `expansion text') => foo =>expansion text undefine(`foo') => foo =>foo
Не будет ошибкой если для имени name не определено макро-расширение.
В таком случае, undefine
просто ничего не делает.
Встроенный макрос undefine
распознается только при наличии параметров.
Переименование макроса
Есть возможность осуществлять переименование любого уже определенного макроса.
Для выполнения этого, необходимо использовать встроенный макрос defn
:
defn(name)
который осуществляет расширение заключенного в кавычки определения имени name. Если аргумент не является определенным макросом, то расширение будет пустым (void).
Если имя name является макросом, определенным пользователем,
то заключенное в кавычки определение будет просто заключенным в кавычки
текстом расширения.
Если имя name является встроенным макросом,
то расширение будет специальным токеном,
который указывает на внутреннее определение встроенного макроса.
Этот токен имеет смысл только как второй аргумент для
define
(и pushdef
),
и игнорируется в любом другом контексте.
Его номальное использование легче понять на примере,
который показывает как переименовать undefine
в zap
:
define(`zap', defn(`undefine')) => zap(`undefine') => undefine(`zap') =>undefine(zap)
Таким способом, defn
может быть использован для копирования макро-определений,
а также определений встроенных макросов.
Даже когда оригинальный макрос удален,
другое имя может быть использовано для доступа к определению.
Встроенный макрос defn
распознается только при наличии параметров.
Временное переопределение макроса
Существует возможность временного переопределения макроса,
с возвращением предыдущего определения позже.
Это выполняется с помощью встроенных макросов pushdef
и popdef
:
pushdef(name [, expansion]) popdef(name)
которые очень подобны встроенным макросам define
и undefine
.
Работа этих макросов очень похожа на работу стека.
С помощью pushdef
макросы переопределяются временно,
поскольку pushdef
заменяет существующее определение имени name,
сохраняя предыдущее определение, перед тем как устанавливает новое.
Если предыдущее определение не существует, то поведение pushdef
подобно define
.
Если макрос имеет несколько определений (из которых доступно только одно),
то самое последнее, "верхнее", определение может быть удалено с помощью popdef
.
Если предидущего определения не существует, то поведение popdef
подобно undefine
.
define(`foo', `Expansion one.') => foo =>Expansion one. pushdef(`foo', `Expansion two.') => foo =>Expansion two. popdef(`foo') => foo =>Expansion one. popdef(`foo') => foo =>foo
Если макрос имеет несколько определений и переопределение выполнялось с помощью define
,
то последнее, "верхнее", определение заменяется новым определением.
Если определение удаляется с помощью undefine
,
то удаляются все определения, а не только "верхнее".
define(`foo', `Expansion one.') => foo =>Expansion one. pushdef(`foo', `Expansion two.') => foo =>Expansion two. define(`foo', `Second expansion two.') => foo =>Second expansion two. undefine(`foo') => foo =>foo
Существует возможность временного переопределения встроенных макросов с помощью
pushdef
и defn
.
Макросы pushdef
и popdef
распознаются только при наличии параметров.
Косвенные вызовы макросов
С помощью indir
любой макрос можетбыть вызван косвенно:
indir(name, ...)
что в результате приводит к вызову макроса с именем name,
которому передаются все оставшиеся аргументы.
Это может быть использовано для вызова макросов с "недопустимыми" именами
(define
позволяет определять такие имена):
define(`$$internal$macro', `Internal macro (name `$0')') => $$internal$macro =>$$internal$macro indir(`$$internal$macro') =>Internal macro (name $$internal$macro)
Смысл заключается в том, что большие пакеты макросов могут иметь определения приватных макросов,
которые не должны быть вызваны каким-либо случайным образом.
Такие приватные макросы могут быть вызваны только
с помощью встроенного макроса indir
.
Косвенные вызовы встроенных макросов
Встроенные макросы могут быть вызваны косвенно с помощью builtin
:
builtin(name, ...)
что в результате приводит к вызову встроенного макроса с именем name, которому передаются все оставшиеся аргументы. Это может быть использовано когда имени name дано другое определение которое скрывает оригинал.
Макрос builtin
распознается только при наличии параметров.
Условия, циклы и рекурсия
Возможность использования макросов которые способны только расширяться в простой текст не достаточна. Нам необходима возможность выполнять различные расширения макросов, в зависимости от условий, возникающих во время выполнения Например, нам необходима возможность учитывать некоторые условия. Кроме того, нам необходима возможность использования каких-либо циклических конструкций, так, чтобы мы могли выполнять что-либо какое-то количество раз, или пока какое-либо условие оценивается как истинное.
Проверка макро-определений
В m4
существует два различных встроенных макроса
для обработки условий.
Первым из них является ifdef
:
ifdef(name, string-1, opt string-2)
который предоставляет возможность проверять определен какой-либо макрос или нет.
Если имя name является определенным макросом,
то ifdef
расширяется в строку string-1,
а в противном случае, в строку string-2.
Если string-2 опущена, то полагается что ее значение - пустая строка
(что согласовано с нормальными правилами).
ifdef(`foo', ``foo' is defined', ``foo' is not defined') =>foo is not defined define(`foo', `') => ifdef(`foo', ``foo' is defined', ``foo' is not defined') =>foo is defined
Макрос ifdef
распознается только при наличии параметров.
Сравнение строк
Другим, более мощным встроенным макросом, для обработки условий, является ifelse
.
В зависимости от числа принимаемых аргументов,
он может быть использован как средсство вставки длинных комментариев,
как конструкция if-else
или как разветвитель:
ifelse(comment) ifelse(string-1, string-2, equal, opt not-equal) ifelse(string-1, string-2, equal, ...)
При использовании только одного аргумента, ifelse
просто отбрасывает аргумент
и не производит вывод.
Это общая идиома m4
для вставки блока комментариев,
используемая как альтернатива использованию повторяющихся dnl
.
Такое специальное использование распознается в GNU m4
,
и, таким образом, никогда не вызывает предупреждения о нехватке аргументов.
Если ifelse
вызывается с тремя или четырьмя аргументами,
то он будет расширяться в equal, когда строки string-1 и string-2
равны. В противном случае, он расширяется в not-equal.
ifelse(foo, bar, `true') => ifelse(foo, foo, `true') =>true ifelse(foo, bar, `true', `false') =>false ifelse(foo, foo, `true', `false') =>true
Однако, ifelse
способен принимать более четырех аргументов.
При предоставлении более четырех аргументов,
ifelse
работает подобно инструкциям case
или switch
традиционных языков программирования.
Если string-1 и string-2 равны, то ifelse
расширяется
в equal.
В противном случае, процедура повторяется, а первые три аргумента отбрасываются.
Пример такого вызова:
ifelse(foo, bar, `third', gnu, gnats, `sixth', `seventh') =>seventh
Обычно, вариант такого использования будет несколько сложнее этого примера.
Наиболее часто ifelse
используется в макросах которые реализуют различные циклы.
Макрос ifelse
распознается только при наличии параметров.
Циклы и рекурсия
m4
не обладает непосредственной поддержкой циклов,
но макросы могут быть рекурсивными.
На глубину вложенности рекурсии не существует никаких ограничений,
кроме тех которые накладываются используемыми оборудованием и опрерационной системой.
Циклы могут быть запрограммированы с помощью использования рекурсии и средств обработки условий, которые были описаны ранее.
Существует встроенный макрос shift
, который, кроме прочего, может
быть использован для итерации фактических параметров макроса:
shift(...)
Он принимает любое число аргументов и расширяется списком всех своих аргументов, кроме первого отделенного запятой аргумента, с каждым аргументом заключенным в кавычки.
shift(bar) => shift(foo, bar, baz) =>bar,baz
Примером использования shift
, является макрос
который изменяет порядок следования его аргументов на противоположный:
define(`reverse', `ifelse($#, 0, , $#, 1, ``$1'', `reverse(shift($@)), `$1'')') => reverse => reverse(foo) =>foo reverse(foo, bar, gnats, and gnus) =>and gnus, gnats, bar, foo
Хотя этот пример макроса не очень интересен, он показывает простой способ
создания цикла с помощью shift
, ifelse
и рекурсии.
Рассмотрим простой пример циклического макроса forloop
.
Он может быть применен, например, для простой организации счета:
forloop(`i', 1, 8, `i ') =>1 2 3 4 5 6 7 8
Аргументы имеют имя для итерационной переменной,
стартовое значение, конечное значение и текст для расширения в каждой итерации.
В этом макросе, макрос i
описан только внутри цикла.
После итерации цикла, он сохраняет предшествующее значение.
Циклы могут быть вложенными, подобно
forloop(`i', 1, 4, `forloop(`j', 1, 8, `(i, j) ') ') =>(1, 1) (1, 2) (1, 3) (1, 4) (1, 5) (1, 6) (1, 7) (1, 8) =>(2, 1) (2, 2) (2, 3) (2, 4) (2, 5) (2, 6) (2, 7) (2, 8) =>(3, 1) (3, 2) (3, 3) (3, 4) (3, 5) (3, 6) (3, 7) (3, 8) =>(4, 1) (4, 2) (4, 3) (4, 4) (4, 5) (4, 6) (4, 7) (4, 8) =>
Реализация макроса цикла forloop
очень проста.
Цикл forloop
сам по себе является оберткой,
которая сохраняет предшествующее определение первого аргумента,
вызывает внутренний макрос _forloop
и переустанавливает сохраненное определение первого аргумента.
Макрос _forloop
однократно расширяет четвертый аргумент
и проверяет условие завершения.
Если условие завершения не выполняется, то он инкрементирует итерационную переменную
(использование предопределенного макроса incr
,
see см. секцию Декремент и инкремент),
и выполняет следующую рекурсию.
Рассмотрим более реальную реализацию forloop
:
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')')')
Следует заметить, что при использовании кавычек требуется внимательность. Только три макро-аргумента не заключены в кавычки, и для каждого аргумента это имеет свой собственный смысл. Попробуйте самостоятельно догадаться почему эти три аргумента не заключены в кавычки, и что произойдет, если их поместить в кавычки.
Теперь, даже несмотря на то, что показанные макросы работоспособны, они не совсем пригодны для повсеместного использования. В них отсутствует даже базовая обработка ошибок в случаях когда начальное значение меньше конечного значения, или когда первый аргумент не является именем. Исправление этих недостатков предоставляется читателю, в качестве упражнения.
Как отлаживать макросы и ввод
При написании макросов для m4
,
очень часто оказывается, что они работают не так как ожидается
(что свойственно многим языкам программирования).
Макропроцессор m4
обеспечивает некоторую поддержку в отладке макросов.
Отображение макро-определений
Если необходимо увидеть во что расширяется какое-либо имя,
можно использовать встроенный макрос dumpdef
:
dumpdef(...)
который принимает любое число аргументов. Если этот встроенный макрос вызван без указания аргументов, то он отображает определения всех известных имен, в противном случае, он отображает определения для указанных имен. Вывод распечатывается непосредственно в стандартный вывод ошибок.
Встроенный макрос dumpdef
имеет пустое (void) расширение.
define(`foo', `Hello world.') => dumpdef(`foo') error-->foo: `Hello world.' => dumpdef(`define') error-->define: <define> =>
Последний пример показывает как отображаются определения встроенных макросов.
Для получения более подробной информации по управлению особенностями отображения см. секцию Управление отладочным выводом.
Трассировка макро-вызовов
С помощью встроенных макросов traceon
и traceoff
,
предоставляется возможность трассировки макро-вызовов и макро-расширений:
traceon(...) traceoff(...)
Когда макросы traceon
и traceoff
вызываются без указания каких-либо аргументов,
то они осуществляют, соответственно, включение (on) или выключение (off) трассировки
для всех определенных макросов.
Когда эти макросы вызываются с аргументами, то они затрагивают
только те макросы, имена которых были указаны при вызове.
Встроенный макросы traceon
и traceoff
имеют пустое (void) расширение.
Всякий раз, при вызове трассируемого макроса, после того как осуществлено накопление его аргументов, производится отображение вызова. Если расширение макро-вызова не пустое (void), то расширение может быть отображено после вызова. Вывод распечатывается непосредственно в стандартный вывод ошибок.
define(`foo', `Hello World.') => define(`echo', `$@') => traceon(`foo', `echo') => foo error-->m4trace: -1- foo -> `Hello World.' =>Hello World. echo(gnus, and gnats) error-->m4trace: -1- echo(`gnus', `and gnats') -> ``gnus',`and gnats'' =>gnus,and gnats
Числа между дефисами отображают глубину вложенности расширения. Каждый уровень вложенности упрощает расширение во внешнем уровне, но уровень вложенности увеличивается когда аргументы содержат макро-вызовы не помещенные в кавычки.
Для получения дополнительной информации по управлению отображением см. секцию Управление отладочным выводом.
Управление отладочным выводом
Опция `-d' командной строки запуска m4
управляет
степенью детализации отладочной информации,
когда используются описанные в предыдущей секции макросы.
В качестве флагов flags, сопровождающих эту опцию, может быть один или более нижеперечисленных флагов:
t
-
Трассировать все макро-вызовы в процессе текущего запуска
m4
. a
-
Показывать все фактические аргументы каждого макро-вызова.
Это применяется для каждого макро-вызова если использован флаг `t'.
В противном случае, только для тех макросов, которые были указаны в вызовах
traceon
. e
-
Показывать расширения для всех макросов, имеющих не пустые (void) расширения.
Это применяется для каждого макро-вызова если использован флаг `t'.
В противном случае, только для тех макросов, которые были указаны в вызовах
traceon
. q
- Отображение фактических аргументов и макро-расширений помещать в кавычки вместе с их текущим размещением в кавычках.
c
- Показывать трассировку в отдельных строках для каждого макро-вызова. Первая строка отображается в момент обнаружения макроса, но до того как произошло накопление его аргументов; вторая строка отображается после того как произошло накопление аргументов; третья строка отображается после того как вызов макроса завершен.
x
- Добавить уникальный идентификатор макро-вызова (`macro call id') к каждой строке вывода трассировки. Это полезно при совместном использовании с флагом `c'.
f
- Показывать имя текущего файла ввода в каждой строке вывода трассировки.
l
- Показывать номер текущей строки ввода в каждой строке вывода трассировки.
p
- Печатать сообщение когда указанный файл обнаружен с помощью механизма поиска файла по заданному пути (path) поиска (см. секцию Поиск включаемых файлов), предоставляя фактически используемое имя файла.
i
- Печатать сообщение при каждой смене текущего файла ввода, предоставляя данное имя файла и номер строки ввода.
V
- Краткая форма одновременного указания всех флагов сразу.
Если при указании опции `-d', то, по умолчанию, автоматически устанавливаются флаги `aeq'. Показанные в двух предыдущих секциях примеры подразумевают использование установки флагов по умолчанию.
Существует встроенный макрос debugmode
,
который позволяет управлять формой отладочного вывода "на лету":
debugmode(opt flags)
Аргумент flags должен быть подмножеством букв, перечисленных выше. Как специальный случай, если аргумент начинается с `+', флаги добавляются к текущим флагам отладки flags, и если аргумент начинается с `и', то флаги удаляются. Если аргументы не указаны, то отладочные флаги установлены в отсутствующие (как будто опция `-d' не была указана), и, наконец, при "пустом" аргументе, флаги устанавливаются в значение по умолчанию.
Сохранение отладочного вывода
Отладочный и трассировочный вывод может быть перенаправлен в файлы
как с помощью использования опции `-o' командной строки m4
,
так и с помощью использования встроенного макроса debugfile
:
debugfile(opt filename)
отправит весь последующий отладочный и трассировочный вывод в файл filename.
Если указано пустое имя файла filename,
то отладочный и трассировочный вывод будет отброшен,
а если макрос debugfile
вызван без аргументов,
то отладочный и трассировочный вывод будет отправлен в стандартный вывод ошибок.
Управление вводом
Эта глава описывает различные встроенные макросы управляющие вводом m4
.
Удаление пробелов из ввода
Встроенный макрос dnl
,
вплоть до первого встреченного символа новой строки,
читает и отбрасывает все символы, включая символ новой строки:
dnl
и он часто используется совместно с макросом define
,
для удаления символов новой строки которые сопровождают вызовdefine
.
Таким образом
define(`foo', `Macro `foo'.')dnl A very simple macro, indeed. foo =>Macro foo.
отбрасывает ввод, вплоть до символа новой строки, и включая символ новой строки, что противоположно трактовке комментариев (см. секцию Комментарии).
Обычно, dnl
немедленно сопровождается символом новой строки
или пробелами.
GNU m4
генерирует предупреждающее сообщение
когда dnl
сопровождается открытой скобкой.
В этом случае,dnl
будет накапливать и обрабатывать все аргументы,
осуществляя поиск парной закрывающей скобки.
При этом будут иметь место все предсказуемые побочные эффекты,
происходящие в результате такого накопления.
dnl
не возвращает вывода.
Ввод, последующий за парной закрывающей скобкой, вплоть до и включая символ новой строки
(на той строке где обнаружена парная скобка), будет отброшен.
Изменение символов кавычек
Символы кавычек (иначе, символы цитирования), установленные по умолчанию,
могут быть заменены с помощью встроенного макроса changequote
:
changequote(opt start, opt end)
где start является новым ограничителем начала цитирования (стартовые кавычки),
а end является новым ограничителем завершения цитирования (завершающие кавычки).
Если любой из аргументов отсутствует,
то вместо отсутствующего аргумента используется соответствующий
символ по умолчанию (`
или '
).
Встроенный макрос changequote
имеет пустое (void) расширение.
changequote([, ]) => define([foo], [Macro [foo].]) => foo =>Macro foo.
Если односимвольные значения не желательны, то start и end могут быть любой длины.
changequote([[, ]]) => define([[foo]], [[Macro [[[foo]]].]]) => foo =>Macro [foo].
Изменение значений символов кавычек (цитирования) на пустые строки будет эффективно блокировать механизм помещения в кавычки (цитирования), не предоставляя способа поместить текст в кавычки (цитировать текст).
define(`foo', `Macro `FOO'.') => changequote(, ) => foo =>Macro `FOO'. `foo' =>`Macro `FOO'.'
В m4
не существует способа поместить строку в кавычки (цитировать строку)
используя какие-либо непарные отсутствующие кавычки,
кроме как использовать changequote
для изменения текущих кавычек.
Никакая строка в кавычках не должна начинаться с буквы или символа подчеркивания `_', поскольку они будут смешиваться с именами во вводе. Выполнение таких действий блокирует мехпнизм цитирования.
Изменение ограничителей комментариев
Ограничители комментариев, принимаемые по умолчанию, могут быть заменены с помощью
встроенного макросаchangecom
:
changecom(opt start, opt end)
где start является новым ограничителем начала комментария,
а end является новым ограничителем завершения комментария.
Если любой из аргументов отсутствует,
то вместо отсутствующего аргумента используется соответствующий
символ по умолчанию (`
и символ новой строки).
Ограничители комментариев могут быть любой длины.
Встроенный макрос changecom
имеет пустое (void) расширение.
define(`comment', `COMMENT') => # A normal comment =># A normal comment changecom(`/*', `*/') => # Not a comment anymore =># Not a COMMENT anymore But: /* this is a comment now */ while this is not a comment =>But: /* this is a comment now */ while this is not a COMMENT
Примечательно, что комментарии копируются в вывод подобно строкам помещенным в кавычки. Если необходимо выполнять расширение текста внутри комментария, то можно поместить в кавычки ограничитель начала комментария.
Вызов макроса changecom
без аргументов полностью блокирует механизм комментирования.
define(`comment', `COMMENT') => changecom => # Not a comment anymore =># Not a COMMENT anymore
Изменение лексической структуры слов
Макрос
changeword
и вся ассоциируемая с ним функциональность является экспериментальными. Все это доступно только в том случае, если при запуске скриптаconfigure
, в процессе инсталляции GNUm4
, была указана опция--enable-changeword
. Эта функциональность может быть изменена или даже удалена в будущем, поэтому на нее не стоит полагаться. Пожалуйста, направьте свои комментарии об этом по адресу, которому необходимо отправлять сообщения об ошибках.
Файл который будет обрабатываться m4
разделен
на строки помещенные в кавычки,
слова (потенциальные имена макросов)
и простые токены (любые другие одиночные символы).
Изначально, любое слово описано с помощью следующего регулярного выражения:
[_a-zA-Z][_a-zA-Z0-9]*
Используя changeword
, вы можете изменить это регулярное выражение.
Ослабление лексических правил m4
может быть полезно (например)
если вы хотите попытаться транслировать файл с числами:
changeword(`[_a-zA-Z0-9]+') define(1, 0) =>1
Ужесточение лексических правил - менее полезно, поскольку это, в основном, сделает некоторые встроенные макросы не доступными. Вы могли бы использовать это для предотвращения непредвиденных вызовов встроенных макросов, например:
define(`_indir', defn(`indir')) changeword(`_[_a-zA-Z0-9]*') esyscmd(foo) _indir(`esyscmd', `ls')
Поскольку m4
конструирует свои слова посимвольно, то
существует ограничение на регулярные выражения,
которые могут быть переданы changeword
.
Таким образом, если ваше регулярное выражение принимает `foo',
то оно должно также принимать `f' и `fo'.
changeword
имеет другую функцию.
Когда принятое регулярное выражение содержит любые подвыражения в скобках,
тогда текст, за пределами первого из них, отбрасывается до поиска символа.
Таким образом:
changecom(`/*', `*/') changeword(`#\([_a-zA-Z0-9]*\)') #esyscmd(ls)
Теперь, m4
требует метку `#' в начале каждого вызова макроса,
благодаря чему m4
может быть использован для препроцессирования
shell-скриптов, без получения "проглатывания" команды shift
,
и "плоского" текста, без потери различных общих слов.
Подстановка макросовm4
базирована на тексте,
в то время как TeX
базируется на токенах
changeword
может изменить это различие.
Например, существует такая же идея представленная в TeX
и в m4
.
Сначала, версия TeX
:
\def\a{\message{Hello}} \catcode`\@=0 \catcode`\\=12 =>@a =>@bye
Теперь, версия m4
:
define(a, `errprint(`Hello')') changeword(`@\([_a-zA-Z0-9]*\)') =>@a
В версии примера TeX
, первая строка описывает макрос a
для печати сообщения `Hello'.
Вторая строка описывает @
для использования вместо \, как escape-символа.
Третья строка описывает \ как нормально печатаемый символ, а не escape-символ.
Четвертая строка вызывает макрос a
.
Таким образом, когда TeX
выполняется на этом файле,
то он отображает сообщение `Hello'.
Когда версия примера m4
пропускается через m4
,
то это выводит `errprint(Hello)'.
Смысл этого заключается в том, что TeX
выполняет лексический анализ макро-определения тогда, когда макрос определен.
m4
выполняет только сохранение текста, откладывая лексический анализ
до момента использования макроса.
Вы должны заметить, что использование changeword
будет значительно замедлять m4
(раз в семь).
Сохранение ввода
Существует возможность сохранить некоторый текст до того как будет обнаружено
окончание нормального ввода.
Текст может быть сохранен, для того чтобы он был вновь прочитан m4
когда нормальный ввод будет исчерпан.
Эта особенность используется обычно для инициализации действий очистки перед нормальным выходом,
например, удаление временных файлов.
Для сохранения текста ввода, необходимо использовать встроенный макрос m4wrap
:
m4wrap(string, ...)
который сохраняет строку string и остальные аргументы в сохранном месте, для того чтобы перечитать вновь, после достижения завершения ввода.
define(`cleanup', `This is the `cleanup' actions. ') => m4wrap(`cleanup') => This is the first and last normal input line. =>This is the first and last normal input line. ^D =>This is the cleanup actions.
Сохраненный ввод перечитывается только когда будет обнаружено окончание нормального ввода
и при этом не будет использован встроенный макрос m4exit
,
для выхода из m4
.
Существует возможность вызвать из сохраненного текста m4wrap
,
но тогда порядок, в котором будет перечитан сохраненный текст, - не определен.
Если m4wrap
не использован рекурсивно,
то сохраненные части текста будут перечитаны в порядке,
противоположном порядку ихнего сохранения,
т.е. последний сохраненный читается первым
(LIFO--last in, first out).
Включение файлов
m4
позволяет включать именованные файлы в любой точке ввода.
Включение именованых файлов
В m4
существует два встроенных макроса для включения файлов:
include(filename) sinclude(filename)
Оба этих макроса позволяют m4
прочитать именованный файл filename.
Когда будет достигнут конец файла, то ввод продолжится из предыдущего файла.
Расширением макросов include
и sinclude
, таким образом,
является содержимое файла filename.
Отсутствие указанного файла будет являться ошибкой.
Если, при отсутствии файлов, вы не хотите получать сообщения об ошибках,
то для включения файлов может быть использован макрос sinclude
,
который расширяется в содержимое файла, если файл существует,
или ни во что не расширяется, когда файл отсутствует.
include(`no-such-file') => error-->30.include:2: m4: Cannot open no-such-file: No such file or directory sinclude(`no-such-file') =>
Предположим, что файл с именем `incl.m4' содержит следующие строки:
Include file start foo Include file end
Обычно, включение файла используется для вставки содержимого файла внутрь потока ввода.
Содержимое файла будет прочитано m4
и макро-вызовы,
расположенные в этом файле, будут расширены:
define(`foo', `FOO') => include(`incl.m4') =>Include file start =>FOO =>Include file end =>
Факт того, что макросы include
и sinclude
расширяются в содержимое файла,
может быть использован для определения макросов которые оперируют на целых файлах.
Существует пример, который определяет макрос `bar'
для расширения содержимого `incl.m4':
define(`bar', include(`incl.m4')) => This is `bar': >>>bar<<< =>This is bar: >>>Include file start =>foo =>Include file end =><<<
Такое использование макроса include
не тривиально,
поскольку файлы могут содержать строки в кавычках, запятые и скобки,
которые могут интерферировать со способом работы парсера m4
.
Встроенные макросы include
и sinclude
распознаются только в случае наличия аргументов.
Поиск включаемых файлов
GNU m4
позволяет, чтобы поиск включаемых файлов осуществлялся не только
в текущем рабочем каталоге, но и в других каталогах.
Если файл не найден в текущем каталоге, и имя файла не является абсолютным, то поиск указанного файла будет осуществляться используя определенные пути поиска. Сначала, поиск осуществляется в каталогах указываемых с помощью опции командной строки `-I', а порядок поиска определяется последовательностью перечисления каталогов. Затем, если установлена переменная окружения `M4PATH', которая содержит список каталогов поиска отделенных друг от друга символом точки с запятой, то поиск осуществляется в соответствии с последовательностью перечисления каталогов в этой переменной окружения.
Если автоматический поиск включаемых файлов вызывает проблемы, то применение отладочного флага `p' (см. секцию Управление отладочным выводом) может помочь изолировать проблему.
Перенаправление (diverting) и отмена перенаправления (undiverting) вывода
Перенаправление (diversions) является способом временного сохранения вывода.
Вывод m4
может быть перенаправлен (diverted) во временный файл
в любой момент времени, и вставлен заново в поток вывода,
путем отмены перенаправления (undiverted), в любой момент времени позже.
Нумерованные перенаправления (diversions) считаются от 0,
перенаправление (diversion) номер 0 является нормальным потоком вывода.
Число одновременных перенаправлений (diversions) ограничено в основном
размерами памяти используемой для их описания,
поскольку GNU m4
пытается хранить перенаправления (diversions) в пямяти.
Однако, существует предел для общего использования памяти всеми перенаправлениями (diversions).
В настоящее время этот предел равен 512K.
При достижении этого максимума,
открывается временный файл для получения содержимого наибольшего перенаправления (diversion)
находящегося в памяти, и освобождения этой памяти для других перенаправлений (diversions).
Таким образом, теоретически возможно, что число перенаправлений (diversions)
будет лимитировано количеством доступных файловых дескрипторов.
Перенаправление (diverting) вывода
Перенаправление вывода осуществляется с помощью использования встроенного макроса
divert
:
divert(opt number)
где число number является перенаправлением (diversion) которое будет использовано. Если число number отсутствует, то подразумевается, что оно нуль.
Встроенный макрос divert
имеет пустое (void) расширение.
Когда весь ввод m4
будет обработан,
то все существующие перенаправления будут автоматически отменены (undiverted),
в соответствии с порядком нумерации.
divert(1) This text is diverted. divert => This text is not diverted. =>This text is not diverted. ^D => =>This text is diverted.
Последовательные вызовы divert
с одним и тем же аргументом
не осуществляют перезапись перенаправленного (diverted) ранее текста,
а добавляют к нему текст.
Если вывод перенаправлен (diverted) в не существующее перенаправление (diversion), то он просто отбрасывается. Это может быть использовано для подавления нежелательного вывода. Общим примером нежелательного вывода являются завершающие символы новой строки после макро-определения. Здесь показано как отменить их.
divert(-1) define(`foo', `Macro `foo'.') define(`bar', `Macro `bar'.') divert =>
Это является общей идиомой программирования в m4
.
Отмена перенаправления (undiverting) вывода
Отмена пренаправления текста может быть указана явно, с помощью встроенного
макроса undivert
:
undivert(opt number, ...)
который отменяет перенаправление в соответствии с указанными аргументами, согласно порядка их указания. Если аргументы не указаны, то осуществляется отмена всех перенаправлений, в соответствии с порядком нумерации.
Встроенный макрос undivert
имеет пустое (void) расширение.
divert(1) This text is diverted. divert => This text is not diverted. =>This text is not diverted. undivert(1) => =>This text is diverted. =>
Примечательны две пустые строки.
Одна из них появляется из-за символа новой строки, следующей за макросом undivert
,
а вторая - из-за символа новой строки, следующей за макросом divert
!
Подобно этому, перенаправление часто начинается с пустой строки.
Когда осуществляется отмена перенапраления перенаправленного текста,
перечитывание ввода m4
не выолняется,
а перенаправленный текст непосредственно копируется в текущий вывод,
и, таким образом, нет ошибок отмены перенаправления внутри перенаправленного текста.
Когда осуществляется отмена перенапраления перенаправленного текста, перенаправленный тект отбрасывается из-за чего нет возможности использовать перенаправленный текст более одного раза.
divert(1) This text is diverted first. divert(0)undivert(1)dnl => =>This text is diverted first. undivert(1) => divert(1) This text is also diverted but not appended. divert(0)undivert(1)dnl => =>This text is also diverted but not appended.
Попытки отменить текущее перенаправление молчаливо игнорируются.
GNU m4
позволяет отменять перенаправление для именованных файлов.
Для данного не численного аргумента,
в текущий вывод будет скопировано без интерпретации содержимое именованного файла.
Это дополняет встроенный макрос include
(см. секцию Включение именованых файлов).
Для иллюстрации различий, предположим, что файл
`foo' содержит слово `bar':
define(`bar', `BAR') => undivert(`foo') =>bar => include(`foo') =>BAR =>
Нумерация перенаправлений (diversion)
divnum
расширяется в номер текущего перенаправления (diversion).
Initial divnum =>Initial 0 divert(1) Diversion one: divnum divert(2) Diversion two: divnum divert => ^D => =>Diversion one: 1 => =>Diversion two: 2
Последний вызов макроса divert
без аргументов необходим,
поскольку не перенаправленный (undiverted) текст будет иначе
перенаправлять (be diverted) сам себя.
Отброс перенаправленного (diverted) текста
Часто, когда вывод перенаправлен (diverted), не известно
будет-ли перенаправленный текст реально необходим.
Поскольку все не пустые перенаправления
возвращаются обратно в основной поток вывода
когда обнаружен конец ввода,
то неодходим метод для отбрасывания перенаправления (diversion).
Если все перенаправления (diversions) должны быть отброшены,
то проще всего завершить ввод m4
вызовом `divert(-1)' который сопровождается
явным `undivert':
divert(1) Diversion one: divnum divert(2) Diversion two: divnum divert(-1) undivert ^D
Не производится никакого вывода вообще.
Очистка выбранных перенаправлений (diversions) может быть выполнена следующими макросами:
define(`cleardivert', `pushdef(`_num', divnum)divert(-1)undivert($@)divert(_num)popdef(`_num')') =>
Вызов осуществляется подобно undivert
,
но результатом будет очистка перенаправлений (diversions),
заданных аргументами.
(Этот макрос имеет одну мерзкую ошибку!
Вы должны попытаться увидеть
если вы обнаружили ее и скорректировали ее)
Макросы для обработки текста
В m4
существует некоторое количество встроенных макросов
для осуществления разного рода манипуляций над текстом:
извлечение подстроки, поиск, замена и т.д.
Определение длины строки
Длина строки может быть вычислена с помощью встроенного макроса len
:
len(string)
который расширяется в длину строки string, как десятичное число.
len() =>0 len(`abcdef') =>6
Встроенный макрос len
распознается только при наличии аргументов.
Поиск подстроки
Поиск подстрок может быть осуществлен с помощью встроенного макроса index
:
index(string, substring)
который расширяется в индекс первого появления подстроки substring
в строке string.
Первый символ в строке string имеет индекс 0.
Если подстрока substring не обнаружена в строке string,
то макрос index
расширяется в `-1'.
index(`gnus, gnats, and armadillos', `nat') =>7 index(`gnus, gnats, and armadillos', `dag') =>-1
Встроенный макрос index
распознается только приналичии аргументов.
Поиск регулярного выражения
Поиск регулярных выражений осуществляется с помощью встроенного макроса regexp
:
regexp(string, regexp, opt replacement)
который осуществляет поиск регулярного выражения regexp в строке string. Для регулярных выражений используется такой же синтаксис как и в GNU Emacs. Для более полной информации обратитесь к секции "Syntax of Regular Expressions" в руководстве по GNU Emacs "The GNU Emacs Manual".
Если строка замены replacement опущена,
то макрос regexp
расширяется в индекс
первого совпадения строки, заданной регулярным выражением regexp,
в строку string.
Если строка, заданная регулярным выражением regexp,
ни с чем не совпадает в строке string,
то макрос regexp
расширяется в -1.
regexp(`GNUs not Unix', `\<[a-z]\w+') =>5 regexp(`GNUs not Unix', `\<Q\w*') =>-1
Если строка замены replacement задана,
то макрос regexp
заменяет расширение в значение этого аргумента
с `\n' подставленным вместо
текста совпадающего с n-м подвыражением строки замены regexp
заключенным в скобки.
Указание `\&' соответствует всему тексту,
который совпадает с регулярным выражением regexp.
regexp(`GNUs not Unix', `\w\(\w+\)$', `*** \& *** \1 ***') =>*** Unix *** nix ***
Встроенный макрос regexp
распознается только приналичии аргументов.
Извлечение подстроки
Извлечение подстроки может быть осуществлено с помощью встроенного макроса substr
:
substr(string, from, opt length)
который расширяется в подстроку строки string, которая начинается в индексе from, и состоит из length символов, или продолжается вплоть до конца строки string, если параметр length отсутствует. Начальный индекс строки всегда 0.
substr(`gnus, gnats, and armadillos', 6) =>gnats, and armadillos substr(`gnus, gnats, and armadillos', 6, 5) =>gnats
Встроенный макрос substr
распознается только приналичии аргументов.
Трансляция символов
Трансляция символов может быть выполнена с помощью встроенного макроса translit
:
translit(string, chars, replacement)
который расширяется в строку string, при этом каждый символ строки string, который обнаружен в строке chars транслируется в символ, с таким же индексом, из строки replacement.
Если строка replacement короче строки chars, то лишние символы удаляются из расширения. Если строка replacement не указана, то все символы строки string, которые присутствуют в строке chars будут удалены из расширения.
Обе строки, chars и replacement, могут содержать указания диапазонов символов, напримен, `a-z' (подразумевая все символы латинского алфавита в нижнем регистре) или `0-9' (подразумевая все цифры). Для включения символа дефиса `-' в строку chars или строку replacement, необходимо поместить его как первый или как последний символ в строке.
Не будет ошибкой если последний символ, указанный в диапазоне, будет `больше' чем первый. В этом случае, диапазон символов перечисляется в обратном порядке, то есть `9-0' подразумевает строку `9876543210'.
translit(`GNUs not Unix', `A-Z') =>s not nix translit(`GNUs not Unix', `a-z', `A-Z') =>GNUS NOT UNIX translit(`GNUs not Unix', `A-Z', `z-a') =>tmfs not fnix
Первый пример удаляет все символы верхнего регистра, второй, преобразует символы нижнего регистра в символы верхнего регистра, и третий, `отзеркаливает' все символы верхнего регистра, в процессе преобразования их в символы нижнего регистра. Первые два случая более общие.
Встроенный макрос translit
распознается только при наличии аргументов.
Подстановка текста с помощью регулярного выражения
Глобальная замена в строке может быть выполнена с помощью встроенного макроса
patsubst
:
patsubst(string, regexp, opt replacement)
который осуществляет поиск совпадений регулярного выражения regexp в строке string и осуществляет замену на строку replacement, при каждом обнаруженном совпадении. Для регулярных выражений используется такой же синтаксис как и в GNU Emacs.
Части строки string, которые не конвертированы в результате совпадения с регулярным выражением regexp, копируются в результат расширения. При обнаружении каждого совпадения, процесс поиска продолжается с конца совпадения, таким образом, символ из строки string никогда не будет подставлен дважды. Если регулярное выражение regexp совпадает со строкой нулевой длины, то начальная позиция для поиска инкрементируется, во избежание бесконечных циклов.
После выполнения замены, и вставки строки replacement в расширение, осуществляется подстановка `\n' вместо текста совпадающего с n-м подвыражением строки регулярного выражения regexp заключенного в скобки. Указание `\&' соответствует всему тексту, который совпадает с регулярным выражением regexp.
Строка replacement может отсутствовать. В этом случае, текст, совпадающий со строкой regexp, будет удален.
patsubst(`GNUs not Unix', `^', `OBS: ') =>OBS: GNUs not Unix patsubst(`GNUs not Unix', `\<', `OBS: ') =>OBS: GNUs OBS: not OBS: Unix patsubst(`GNUs not Unix', `\w*', `(\&)') =>(GNUs)() (not)() (Unix) patsubst(`GNUs not Unix', `\w+', `(\&)') =>(GNUs) (not) (Unix) patsubst(`GNUs not Unix', `[A-Z][a-z]+') =>GN not
Здесь показан более реалистичный пример,
котоорый осуществляет капитализацию (первый символ в верхнем регистре, а остальные - в нижнем)
слова или целой фразы,
заменяя вызововы макросов upcase
и downcase
в строках.
define(`upcase', `translit(`$*', `a-z', `A-Z')')dnl define(`downcase', `translit(`$*', `A-Z', `a-z')')dnl define(`capitalize1', `regexp(`$1', `^\(\w\)\(\w*\)', `upcase(`\1')`'downcase(`\2')')')dnl define(`capitalize', `patsubst(`$1', `\w+', `capitalize1(`\&')')')dnl capitalize(`GNUs not Unix') =>Gnus Not Unix
Встроенный макрос patsubst
распознается только при наличии аргументов.
Форматирование вывода
Форматирование вывода может быть выполнено с помощью встроенного макроса format
:
format(format-string, ...)
который работает подобно функции printf
языка C.
Первый аргумент является форматируемой строкой,
которая может содержать `%' спецификации,
а расширением макроса format
будет форматированная строка.
Использование этого встроенного макроса лучше всего пояснить на нескольких примерах:
define(`foo', `The brown fox jumped over the lazy dog') => format(`The string "%s" is %d characters long', foo, len(foo)) =>The string "The brown fox jumped over the lazy dog" is 38 characters long
Использование встроенного макроса forloop
описывается в секции
Циклы и рекурсия,
этот пример показывает как использовать макрос format
для получения табулированного вывода.
forloop(`i', 1, 10, `format(`%6d squared is %10d ', i, eval(i**2))') => 1 squared is 1 => 2 squared is 4 => 3 squared is 9 => 4 squared is 16 => 5 squared is 25 => 6 squared is 36 => 7 squared is 49 => 8 squared is 64 => 9 squared is 81 => 10 squared is 100
Встроенный макрос format
смоделирован подобно функции `printf'
языка программирования ANSI C,
и он поддерживает обычные `%' спецификаторы:
`c', `s', `d', `o', `x',
`X', `u', `e', `E' and `f';
он поддерживает указание ширин и точности полей, а также модификаторы
`+', `-', ` ', `0',
`#', `h' and `l'.
Для получения информации, более детально описывающей функционирование printf
,
следует обратиться к руководству по библиотеке языка программирования C.
Арифметические макросы
Целочисленная арифметика, включенная в m4
,
использует C-подобный синтаксис.
Как согласованные сокращения,
существуют встроенные макросы для выполнения операций простого инкрементирования и декрементирования.
Декремент и инкремент
Инкремент и декремент целочисленных значений поддерживается использованием встроенных макросов
incr
и decr
:
incr(number) decr(number)
которые расширяются в соответственно инкрементированное или декрементированное на единицу численное значение number.
incr(4) =>5 decr(7) =>6
Встроенные макросы incr
и decr
распознаются только при наличии
аргументов.
Оценка значений целочисленных выражений
Целочисленные выражения могут быть оценены с помощью встроенного макроса eval
:
eval(expression, opt radix, opt width)
который расширяется в значение выражения expression.
Выражения могут содержать следующие знаки операций, которые перечисляются в порядке уменьшения приоритета.
-
- Унарный минус
**
- Возведение в степень
* / %
- Умножение, деление и модуль
+ -
- Сложение и вычитание
<< >>
- Сдвиг влево или вправо
== != > >= < <=
- Операции отношения
!
- Логическое отрицание
~
- Побитное отрицание
&
- Побитное "И" ("AND")
^
- Побитное "ИСКЛЮЧАЮЩЕЕ ИЛИ" ("XOR")
|
- Побитное "ИЛИ" ("OR")
&&
- Логическое "И" ("AND")
||
- Логическое "ИЛИ" ("OR")
Все знаки операций, кроме возведения в степень, лево-ассоциированные.
Примечательно, что множество реализаций m4
используют `^' как альтернативный знак операции для возведения в степень,
в то время как множество других реализаций используют `^' как
побитное "ИСКЛЮЧАЮЩЕЕ ИЛИ" ("XOR").
GNU m4
изменил его поведение:
раньше `^' использовался для возведения в степень,
а теперь - для вычисления побитного "ИСКЛЮЧАЮЩЕЕ ИЛИ" ("XOR").
Числа без специального префикса являются десятичными. Простой префикс `0' указывает на то, что число восьмеричное. `0x' указывает шестнадцатеричное число. `0b' указывает двоичное число. `0r' указывает число имеющего основание системы счисления от 1 до 36: префикс немедленно сопровождается десятичным выражением указывающим основание системы счисления, после которого следует двоеточие, а затем, цифры являющиеся числом. Для любого основания системы счисления используются следующие цифры: `0', `1', `2', .... после `9', цифрами являются `a', `b' ... вплоть до `z'. Буквы верхнего и нижнего регистра могут быть использованы взаимозаменяемо в числах префикса и числах, префиксах и как цифры чисел.
При необходимости, для группирования подвыражений могут быть использованы скобки.
Операции отношения возвращают 1
, в случае результата "истина" (True
),
и 0
, в случае результата "ложь" (False
).
Здесь представлено несколько примеров использования встроенного макроса eval
.
eval(-3 * 5) =>-15 eval(index(`Hello world', `llo') >= 0) =>1 define(`square', `eval(($1)**2)') => square(9) =>81 square(square(5)+1) =>676 define(`foo', `666') => eval(`foo'/6) error-->51.eval:14: m4: Bad expression in eval: foo/6 => eval(foo/6) =>111
Второй особенностью, которую показывает последний пример,
является то, что встроенный макрос eval
не обрабатывает макро-имена,
даже если они расширяются в допустивые выражения
(или часть допустимого выражения).
Следовательно, все макросы должны быть расширены до того как они будут переданы
макросу eval
.
Если указано основание системы счисления radix,
то оно определяет основание системы счисления, используемое в расширении.
По умолчанию, основание системы счисления 10.
Результат eval
всегда имеет знак.
Аргумент width специфицирует минимальную ширину вывода.
Результат дополняется нулями для дополнения текста расширения до требуемой ширины.
eval(666, 10) =>666 eval(666, 11) =>556 eval(666, 6) =>3030 eval(666, 6, 10) =>0000003030 eval(-666, 6, 10) =>-000003030
Следует заметить, что основание системы счисления radix не может быть больше чем 36.
Встроенный макрос eval
распознается только при наличии аргументов.
Выполнение команд UNIX
Существует несколько встроенных макросов m4
которые позволяют
запускать на выполнение команды UNIX из m4
.
Выполнение простых команд
Любая команда командного интерпритатора shell может быть выполнена
с помощью использования встроенного макроса syscmd
:
syscmd(shell-command)
который запускает на выполнение команду shell-command как команду командного интерпритатора shell.
Встроенный макрос syscmd
имеет пустое (void) расширение,
а не вывод команды shell-command!
Вывод или сообщения об ошибках команды shell-command
не читаются m4
непосредственно.
См. секцию Чтение вывода команд
если вам необходимо обрабатывать вывод команды.
Перед запуском команды, m4
"сбрасывает" свои буферы вывода.
По умолчанию, стандартный ввод, вывод и вывод ошибок для shell-command
являются теми же самыми, что и для m4
.
Встроенный макрос syscmd
распознается только при наличии аргументов.
Чтение вывода команд
Если необходимо прочитать вывод команды UNIX,
то необходимо использовать встроенный макрос
esyscmd
:
esyscmd(shell-command)
который расширяет стандартный вывод команды командного интерпретатора shell-command.
Перед запуском команды, m4
"сбрасывает" свои буферы вывода.
По умолчанию, стандартный ввод и вывод ошибок для shell-command
являются теми же самыми, что и для m4
.
Вывод ошибок команды shell-command
не является частью расширения макроса:
он появляется как часть вывода ошибок m4
.
Предположим, что вы находитесь в каталоге `checks'
дистрибутива GNU m4
, тогда:
define(`vice', `esyscmd(grep Vice ../COPYING)') => vice => Ty Coon, President of Vice =>
Примечательно, что расширение макроса esyscmd
имеет сопровождающие
символы новой строки.
Встроенный макрос esyscmd
распознается только при наличии аргументов.
Коды завершения
Для проверки результата завершения команды командного интерпретатора,
можно использовать встроенный макрос sysval
:
sysval
который расширяется в статус завершения последней выполненной
с помощью макроса syscmd
или esyscmd
команды командного интерпретатора.
syscmd(`false') => ifelse(sysval, 0, zero, non-zero) =>non-zero syscmd(`true') => sysval =>0
Создание имен для временных файлов
Крманды командного интерпретатора, которые запускаются с помощью встроенных макросов
syscmd
и/или esyscmd
,
могут нуждаться во временных файлах для вывода или каких-либо других нужд.
Существует встроенный макрос maketemp
позволяющий создавать имена для временных файлов:
maketemp(template)
который расширяется в имя несуществующего файла,
созданного из строки template,
которая должна заканчиваться строкой `XXXXXX'.
Шесть символов X
будет заменено,
как правило чем-либо включающим идентификатор процесса m4
,
что позволяет создать уникальное имя файла.
maketemp(`/tmp/fooXXXXXX') =>/tmp/fooa07346 maketemp(`/tmp/fooXXXXXX') =>/tmp/fooa07346
Как показано в примере, несколько вызовов макроса maketemp
может быть расширено в одну и ту же строку,
поскольку критерием выбора является существование файла.
Если файл не был создан до следующего вызова,
то два последовательных вызова могут быть расширены в одно и то же имя файла.
Встроенный макрос maketemp
распознается только при наличии аргументов.
Различные встроенные макросы
Эта глава описывает различные встроенные макросы, которые реально не могли принадлежать какой-либо из предыдущих глав.
Печать сообщений об ошибках
Сооьщения об ошибках могут быть напечатаны с помощью использования встроенного макроса
errprint
:
errprint(message, ...)
который просто печатает строку сообщения message и остальные аргументы в стандартный вывод ошибок.
Встроенный макрос errprint
имеет пустое (void) расширение.
errprint(`Illegal arguments to forloop ') error-->Illegal arguments to forloop =>
Сопровождающие символы новой строки не печатаются автоматически,
таким образом, они должны обеспечиваться как часть аргумента,
как показано в примере выше.
(Клоны m4
для BSD выполняют добавление сопровождающих символов новой строки
при каждом вызове макроса errprint
).
Для обеспечения возможности определения места возникновения ошибки существуют два полезных встроенных макроса:
__file__ __line__
которые соответственно расширяются в помещенное в кавычки имя текущего файла ввода и в номер строки, в этом файле.
errprint(`m4:'__file__:__line__: `Input error ') error-->m4:56.errprint:2: Input error =>
Выход из m4
Если необходимо выйти из m4
, до того как будет прочитан весь ввод,
можно использовать встроенный макрос m4exit
:
m4exit(opt code)
который приводит к тому, что m4
завершает свою работу
с кодом возврата code.
Если код возврата code не указан, то код возврата m4
будет нуль.
define(`fatal_error', `errprint(`m4: '__file__: __line__`: fatal error: $* ')m4exit(1)') => fatal_error(`This is a BAD one, buster') error-->m4: 57.m4exit: 5: fatal error: This is a BAD one, buster
После вызова этого макроса, m4
завершит свою работу с кодом возврата 1.
Этот макрос предназначен только для выхода по ошибке,
поскольку нормальная процедура выхода, в этом случае, не выполняется,
например, не выполняется отмена перенаправления (undivert)
для перенаправленного (diverted) текста
и не выполняется перечитывание сохраненного текста
(См. секцию Сохранение ввода).
Быстрая загрузка "замороженных" состояний
Некоторые большие приложения m4
могут быть построены
на основе общей программной базы, которая содержит сотни различных определений
и множество дополнительных ресурсоемких инициализаций.
Обычно, такая общая база хранится в одном или нескольких файлах описаний,
которые перечисляются при каждом запуске m4
перед файлами ввода пользователя, или подключаются
в файлах ввода пользователя с помощью include
.
Каждое повторное перечитывание общей базы большого приложения
может привести к большим затратам времени.
Для ускорения запуска приложений использующих объемную общую базу,
GNU m4
предлагает некоторые вспомогательные механизмы.
Предположим, что пользователь постоянно повторяет использование:
m4 base.m4 input.m4
с несколько изменяющимся содержимым файла `input.m4', но неизменным содержимым в файле `base.m4'. Тогда, пользователю лучше один раз использовать:
m4 -F base.m4f base.m4
а во всех последующих запусках использовать:
m4 -R base.m4f input.m4
с различными изменениями ввода в `input.m4'.
Первый запуск m4
, содержащий опцию -F
,
осуществляет только чтение и обработку файла `base.m4',
в котором выполняется определение различных макросов приложения и прочие вычисления,
необходимые для какой-либо общей начальной инициализации всего приложения.
После того как файл ввода `base.m4' будет полностью обработан,
GNU m4
сгенерирует файл `base.m4f', который называют
"замороженным" файлом, поскольку он содержит своеобразный образ
внутреннего состояния m4
после обработки файла ввода `base.m4'.
Последующие запуски m4
, содержащие опцию -R
,
способны загрузить в память образ внутреннего состояния m4
после обработки файла ввода `base.m4' из файла `base.m4f',
и это осуществляется перед тем как начать чтение любого другого файла ввода.
Такой подход подразумевает,
что вместо того чтобы каждый раз начинать обработку большого приложения
с выполнения повторной обработки одного и того же ввода m4
,
ввод m4
будет читаться только после восстановления состояния,
сохраненного в результате предыдущего запуска.
В нашем примере, после восстановления состояния из файла `base.m4f',
эффект будет таким же самым как и после повторной обработки файла `base.m4'.
Однако, восстановление состояния из файла `base.m4f'
выполняется намного быстрее.
Только один "замороженный" файл может быть создан или прочитан
в результате одного запуска m4
.
Нельзя осуществить восстановление внутреннего состояния m4
из двух "замороженных" файлов одновременно.
Однако,
"замороженные" файлы могут быть инкрементно обновлены
с помощью совместного последовательного использования опций
-R
и -F
.
Например, команда запуска m4
:
m4 file1.m4 file2.m4 file3.m4 file4.m4
может быть разделена на следующую последовательность запусков m4
,
накапливающую одинаковый вывод:
m4 -F file1.m4f file1.m4 m4 -R file1.m4f -F file2.m4f file2.m4 m4 -R file2.m4f -F file3.m4f file3.m4 m4 -R file3.m4f file4.m4
При этом необходима определенная осторожность,
поскольку не во всех абсолютно случаях
подобная попытка будет выполняться одинаково корректно.
В частности, не обрабатываются атрибуты трассировки макросов
и не обрабатываются текущие установки для changeword
.
Также, взаимодействие некоторых опций m4
,
которое использовалось при одном запуске и не используемое для следующего запуска,
в настоящий момент еще полностью не проанализировано.
С другой стороны,
вы должны быть уверены в том, что стек определений pushdef
обрабатывается корректно,
а это затрагивает undefine
или переименование встроенных макросов,
а также изменение вида строк для кавычек и комментариев.
Когда какой-либо запуск m4
"заморожен",
то запрещена автоматическая отмена перенаправления (undiversion),
которая осуществляется в конце обработки.
Вместо этого,
все позитивно нумерованные перенаправления (diversions)
сохраняются в "замороженном" файле.
Передается также номер активного перенаправления (diversion).
Загружаемый "замороженный" файл не обязан находиться в текущем каталоге.
Его поиск осуществляется также как и поиск включаемых,
с помощью include
, файлов
(См. секцию Поиск включаемых файлов).
"Замороженные" файлы могут быть общими для целой архитектуры.
Не будет опасно записать "замороженный" файл на одной машине и прочитать его на другой машине,
подразумевая, что вторая машина использует ту же самую
или более новую версию GNU m4
.
Существуют простые (редактируемые) текстовые файлы,
состоящие из директив,
каждый из которых начинается с прописной (большой) буквы и заканчивается
символом новой строки
(NL).
В любом месте, где ожидается появление директивы,
символ # вставляет строку комментария,
пустые строки - игнорируются.
В следующем описании,
длина length всегда ссылается на соответствующую строку string.
Все числа представлены как десятичные.
Директивами являются:
V number NL
- Подтверждает формат файла. number должно быть 1.
C length1 , length2 NL string1 string2 NL
- Использовать строку string1 и строку string2 как строки начала и конца комментария.
Q length1 , length2 NL string1 string2 NL
- Использовать строку string1 и строку string2 как строки начала и конца кавычек (цитирования).
F length1 , length2 NL string1 string2 NL
-
Определяет, с помощью
pushdef
, определение для строки string1 как расширяющееся в функцию, которая имеет имя встроенного макроса как строку string2. T length1 , length2 NL string1 string2 NL
-
Определяет, с помощью
pushdef
, определение для строки string1 как расширяющееся в текст, который указан как строка string2. D number, length NL string NL
-
Выбирает перенаправление (diversion) number как текущее,
после чего копирует строку string в текущее перенаправление.
Число number может быть отрицательным числом,
для не существующего перенаправления.
Для единственного специфицирования активного выбора,
используйте эту команду с пустой строкой string.
При указании перенаправления (diversion) number как 0,
строка string будет выдана в стандартный вывод во время перезагрузки,
однако, это может быть не выполнено изнутри
m4
.
Совместимость с другими версиями m4
Эта глава описывает различия между этой реализацией m4
и реализацией m4
в системе UNIX, в частности, System V,
Release 3.
Существуют также отличия от клонов m4
для BSD.
Здесь не делается попытка обобщить эти отличия.
Расширения в GNU m4
Эта версия m4
содержит несколько свойств,
которые отсутствуют в версии m4
для System V.
Все эти дополнительные свойства могут быть подавлены
с помощью использования опции командной строки `-G',
если она не будет отвергнута другими опциями командной строки.
-
При использовании
$
n нотации для макро-аргументов, n может состоять из нескольки цифр, в то время как версияm4
для System V допускает использование только одной цифры. Это позволяет макросам в GNUm4
принимать любое число аргументов, а не только девять (См. секцию Аргументы для макроса). -
Поиск файлов, включаемых с помощью использования
include
иsinclude
, осуществляется в путях поиска, определяемых пользователем, если они отсутствуют в текущем рабочем каталоге. Пути поиска определяются с помощью опции командной строки `-I' и содержимым переменной окружения `M4PATH' (См. секцию Поиск включаемых файлов). -
Аргументы макроса
undivert
могут быть не числовыми, в таком случае, именованный файл будет включен в вывод без какой-либо интерпретации (См. секцию Отмена перенаправления (undiverting) вывода). -
Поддержка форматированного вывода осуществляется с помощью встроенного макроса
format
, работа которого спроектирована подобно функцииprintf
библиотеки языка C (См. секцию Форматирование вывода). -
Поиск и подстановка (замена) текста осуществляется с помощью встроенных макросов
regexp
(См. секцию Поиск регулярного выражения) иpatsubst
(См. секцию Подстановка текста с помощью регулярного выражения). -
Вывод команд командного интерпретатора shell может быть прочитан в
m4
с помощью встроенного макросаesyscmd
(См. секцию Чтение вывода команд). -
С помощью использования встроенного макроса
builtin
, существует косвенный доступ к любому встроенному макросу (См. секцию Косвенные вызовы встроенных макросов). -
Макросы могут быть вызваны косвенно с помощью встроенного макроса
indir
(См. секцию Косвенные вызовы макросов). -
Имя текущего файла ввода и текущий номер строки ввода может быть получен
с помощью встроенных макросов
__file__
и__line__
(См. секцию Печать сообщений об ошибках). -
С помощью встроенного макроса
dumpdef
, можно управлять форматом вывода, а с помощью встроенного макросаdebugmode
можно управлять отладочной трассировкой макросов (См. секцию Управление отладочным выводом). -
С помощью встроенного макроса
debugfile
, можно управлять назначением вывода трассировки и отладки (См. секцию Сохранение отладочного вывода).
В дополнение к перечисленным выше расширениям, GNU m4
реализовывает следующие опции командной строки:
`-F', `-G', `-I',
`-L', `-R', `-V', `-W', `-d',
`-l', `-o' and `-t'.
Действие этих опций описывается в секции
Запуск m4
.
Кроме того, стоит заметить, что отладочные и трассировочные способности GNU m4
более обширны, по сравнению с другими версиями m4
.
Возможности версии m4
для System V, отсутствующие в GNU m4
Версия m4
для System V содержит некоторые особенности,
которые еще не реализованы в версии GNU m4
.
-
Версия
m4
для System V поддерживает множество аргументов для встроенного макросаdefn
. Это не реализовано в версии GNUm4
. Полезность такой особенности для меня не очень очевидна.
Прочие несовместимости
Существует еще несколько несовместимостей между этой реализацией m4
и версией m4
для System V.
-
Версия GNU
m4
, при перенаправлении (divertion) текста, реализует синхронизацию строк отлично от версииm4
для System V. GNUm4
выводит строки синхронизации в момент перенаправления (divert) текста, в то время как версияm4
для System V выводит строки синхронизации в момент отмены перенаправления (undivert) текста. Проблемой является то, какие строки и имена файлов будут прикреплены к тексту который был или будет перенаправлен (diverted). Версияm4
для System V полагает, что весь перенаправленный (diverted) текст сгенерирован из исходной строки содержащей вызовundivert
, в то время как версия GNUm4
полагает, что весь перенаправленный (diverted) текст сгенерирован в момент перенаправления (divert). Я ожидаю, что опция строки синхронизации должна, в основном, использоваться когдаm4
используется как препроцессор какого-либо компилятора. Если перенаправленная (diverted) строка приводит к ошибке компилятора, то сообщение об ошибке, в основном, будет относиться к тому месту где было выполнено перенаправление, а не к месту где строка была вставлена. -
Версия GNU
m4
не делает попытки запретить автоссылающиеся определения вида:define(`x', `x') define(`x', `x ')
По существу, нет ничего плохого в определении того, что `x' будет возвращать `x'. Плохой вещью является то, что `x' будет расширяться без кавычек. Вm4
, кто-то может использовать макросы для хранения строк, также как это делается для переменных в других языках программирования, для дальнейшей их проверки с помощью:ifelse(defn(`holder'), `value', ...)
В случаях подобных этому, запрещение содержать собственное имя для макроса будет бесполезным ограничением. Конечно, это предоставляет для пользователей GNUm4
веревку для "подвешивания" самих себя! При более тщательном программировании, можно избежать пересканирование "зависаний", несколько подобно тому как это делается для бесконечных циклов при традиционном программировании. -
GNU
m4
без опции `-G' будет определять, что макро__gnu__
будет расширен в пустую строку. В системах UNIX, GNUm4
без опции `-G' будет определять макрос__unix__
, в другом случае - макросunix
. Оба макроса расширяются в пустую строку.