> For the complete documentation index, see [llms.txt](https://ayakaleaf-pro.ayaka.space/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ayakaleaf-pro.ayaka.space/latex/ru/drugie-temy/24-how-tex-macros-actually-work-part-6.md).

# Как на самом деле работают макросы TeX: часть 6

[Часть 1](/latex/ru/drugie-temy/19-how-tex-macros-actually-work-part-1.md) [Часть 2](/latex/ru/drugie-temy/20-how-tex-macros-actually-work-part-2.md) [Часть 3](/latex/ru/drugie-temy/21-how-tex-macros-actually-work-part-3.md) [Часть 4](/latex/ru/drugie-temy/22-how-tex-macros-actually-work-part-4.md) [Часть 5](/latex/ru/drugie-temy/23-how-tex-macros-actually-work-part-5.md) [Часть 6](/latex/ru/drugie-temy/24-how-tex-macros-actually-work-part-6.md)

## Введение и обзор: Что было ранее

В предыдущих 5 частях этой серии мы увидели:

* как TeX читает символы внутри входного файла и использует коды категорий, чтобы распознавать разные «классы» символов, а затем преобразовывать их в символьные токены и командные токены;
* что макрос, по сути, состоит из четырех частей:

```
<примитив макроса TeX><имя макроса><текст параметров>{<текст замены>}
```

где:

* `<примитив макроса TeX>` = один из `\def`, `\edef`, `\gdef` или `\xdef`;
* `<имя макроса>`=имя вашего макроса, например `\foo`;
* `<текст параметров>` может быть «пустым» (отсутствовать) или может быть строкой токенов-разделителей и токенов параметров макроса;
* `<текст замены>` — это фактическое тело вашего макроса: та часть, которая «выполняется» (расширяется) при вызове макроса.
* как `<текст параметров>` раздел может содержать широкий диапазон токенов и что TeX использует этот раздел как «шаблон токенов», чтобы сопоставить вызов макроса с его исходным определением и выяснить аргументы, используемые с макросом, — и как TeX ожидает, что использование вами макроса будет соответствовать его исходному определению;
* что внутри TeX определение макроса хранится как непрерывная последовательность токенов, представляющих `<текст параметров>` и `<текст замены>` разделы.

Когда вы используете команду макроса, TeX сначала проверяет, принимает ли она какие-либо параметры. Если да, то TeX затем должен определить фактические *аргументы* используемые в вашем вызове макроса. TeX должен проверить ваш вызов макроса по определению «шаблона токенов», которое он хранит в памяти. В частности, TeX использует свое внутреннее (хранимое) определение `<текст параметров>` раздела как шаблон, с помощью которого он может выделить *токены* то, что является фактическими аргументами, и то, что *токены* просто служит разделителями.

## Смысл расширения макроса

Теперь мы, наконец, готовы перейти к самой важной теме: как TeX обрабатывает аргументы макроса и фактически выполняет макрос: процесс, который TeX называет *расширением макроса*.

### Но сначала короткий пример: что-то странное?

Чтобы «создать контекст» для объяснения механизма, с помощью которого TeX обрабатывает макросы и их аргументы, мы воспользуемся коротким примером, чтобы обозначить вопросы, которые нам нужно рассмотреть.

#### Аргументы сначала преобразуются в токены

Следующий пример основан на одном из примеров, обсуждаемых на страницах 114–115 [The Advanced TeXbook](https://www.amazon.co.uk/Advanced-Texbook-David-Salomon/dp/0387945563) написанной Дэвидом Соломоном. Он выбран потому, что очень удачно воплощает центральные идеи в очень коротком TeX-макросе.

Во время обычной работы TeX/LaTeX знак `$` имеет код категории 3 («математический сдвиг»), который переводит TeX в/из строчного математического режима (`$...$`) или режима формульного отображения (`$$...$$`) — разумеется, LaTeX использует `\(..\)` и `\[..\]` для тех же целей.

Предположим, нам нужен макрос, который меняет код категории у знака `$` на, скажем, 11, чтобы мы могли набирать его как обычный символ. Мы можем использовать примитивную команду TeX `\catcode` и наша первая попытка такого макроса, `\docat`, может выглядеть так

```
\def\docat #1{\catcode`\$=11 #1}
```

Однако когда мы пытаемся использовать его вот так

```
\begin{document}
\def\docat #1{\catcode`\$=11 #1}
Я заплатил \docat{$90} за ту книгу.
\end{document}
```

мы ожидаем, что TeX наберет `Я заплатил $90 за ту книгу.` но это завершается сообщением об ошибке:

```
! Вставлен пропущенный $.
<вставленный текст>
                $
<будет прочитано снова>
                   \par
l.7
```

Из ошибки кажется, что `$` использованный в аргументе нашего макроса по-прежнему заставляет TeX набирать математику; очевидно, TeX *не* изменил код категории у `$` использованного в аргументе нашего макроса (`$90`). Вопрос в том, *почему* почему TeX не изменил код категории у `$` на 11 и не набрал его как обычный символ? Короткий ответ таков: TeX сначала преобразует аргументы макроса в токены **до** и затем подает их в список токенов `<текст замены>`— но мы рассмотрим лежащие в основе механизмы гораздо подробнее.

Нам нужно помнить, что наше представление о том, что TeX использует текст/символы, относится только к содержимому файла, который TeX читает: как только TeX прочитал какие-либо символы, мы оказываемся в мире *токены*. Вызовы макросов TeX работают с *токены*, а не с фактическими *письменным/текстовым представлением* команд TeX/LaTeX — это станет яснее по мере разбора примера.

Сначала мы можем подумать, что наше использование `\docat` макроса в `Я заплатил \docat{$90} за ту книгу.` то же самое, что и прямой набор эквивалентного кода TeX (или LaTeX), например следующего, который *делает* работает:

```
\begin{document}
Я заплатил \catcode`\$=11 $90 за ту книгу.
\end{document}
```

![Некоторый код TeX, выполняющийся в Overleaf](/files/d72715917a9f2ceb3ee266eb7ea0690dd885e440)

Однако, как мы видели выше, способ, которым TeX обрабатывает аргументы макроса, дает результат (`! Вставлен пропущенный $.`), который существенно отличается от прямого написания кода TeX: теперь мы рассмотрим *почему* то, как это происходит.

### Макросы и аргументы как списки токенов

Чтобы полностью понять поведение `\docat` макроса и его аргумента (`$90`), а также почему он не работает, нам снова нужно представить определение `\docat` макроса и любых используемых аргументов (когда вызывается \docat) как *списки токенов*, а не как последовательность символов.

Когда TeX просматривает ваш входной текст, он распознает `\docat` как команду макроса; после этого он проверяет, принимает ли она какие-либо параметры — то, как TeX это делает, объясняется в следующем разделе для читателей, интересующихся тонкими деталями.

#### Для тех, кто любит детали...

После вызова макроса TeX проверяет, является ли самый первый токен (в хранимом списке токенов определения макроса) **токеном конца сопоставления** : если да, TeX может быть уверен, что макрос не принимает никаких параметров.

**Пример**

Следующие диаграммы списков узлов сравнивают списки токенов для двух макросов:

* `\def\foo A#1B{#1}`: здесь есть `<текст параметров>` для `A#1B`, следовательно, токен **токеном конца сопоставления** токен **не** является первым токеном, поэтому TeX продолжил бы поиск параметров;
* `\def\foo{X}`: здесь нет `<текст параметров>` раздела, следовательно, токен **токеном конца сопоставления** является первым в списке токенов, и TeX знает, что не нужно искать какие-либо параметры.

![Как TeX проверяет, принимает ли макрос параметры](/files/a7c5484f53cd56066a15dfc4e8c50d9ea9ddc2e0)

## К «грандиозному финалу»: расширение

Напомним себе вопрос: почему следующий макрос не сработал; то есть почему TeX не меняет код категории у каких-либо `$` знаков, используемых в аргументе `\docat` макроса, например `\docat{$90}`?

```
\begin{document}
\def\docat #1{\catcode`\$=11 #1}
Я заплатил \docat{$90} за ту книгу.
\end{document}
```

Как объяснялось выше, когда TeX просматривает ваш ввод и распознает команду макроса — в момент, когда TeX собирается ее выполнить, — TeX сначала проверяет, принимает ли этот макрос какие-либо параметры. Если да, TeX должен будет дополнительно просмотреть входной файл, чтобы определить фактические *аргументы* которые пользователь предоставил для данного конкретного вызова макроса: TeX должен сделать это **до** до того, как он сможет вызвать фактический код макроса. Очевидно, TeX должен определить данные, которые пользователь хочет передать макросу.

Чтобы определить аргументы, присутствующие во входных данных (вызове макроса пользователя), TeX будет руководствоваться внутренне хранимым определением этого макроса: а именно, `<текст параметров>` разделом хранимого определения макроса (списка токенов), который представляет собой своего рода «шаблон токенов». Используя этот «шаблон токенов», TeX должен определить, какие *токены* во вызове макроса пользователя являются просто *разделители* (по сути «знаками пунктуации») и какие *токены* являются частью *аргумента*. Именно когда TeX встречает **токен параметра сопоставления** в хранимом определении макроса `<текст параметров>` разделе («шаблоне токенов»), он понимает, что нужно начать формировать *список токенов* для этого конкретного аргумента.

Как только TeX распознает необходимость определить аргумент пользователя, он просканирует ввод, чтобы сгенерировать токены, и очень тщательно сверит их, токен за токеном, с хранимым определением макроса. TeX продолжает собирать токены для аргумента, пока не обнаружит токен, который на самом деле является разделителем, или пока не обнаружит **токеном конца сопоставления** токен: в любом случае TeX тогда понимает, что пора прекратить поиск токенов, составляющих часть этого аргумента.

### Почему макрос \docat не сработал

Как было отмечено, *до* Чтобы TeX действительно смог вызвать макрос, он должен определить и подготовить все аргументы, которые будут использоваться с этим макросом. Однако чтобы определить аргумент(ы), готовые для передачи в макрос, TeX должен сгенерировать каждый аргумент как *список токенов*: и именно в этом причина `\docat`сбоя.

В нашем примере мы передали `\docat` с аргументом `$90` но этот аргумент сначала *преобразуется в список токенов* по мере того как TeX просматривает вызов макроса — аргумент преобразуется в токены *до* до того, как макрос фактически вызван. Здесь, для аргумента `$90`, TeX сгенерирует три символьных токена: по одному токену для каждого из `$`, `9` и `0`.

Следующая иллюстрация показывает список токенов, сгенерированный для аргумента `$90`, до передачи в тело `\docat` макроса:

![Список токенов TeX, сгенерированный для аргумента макроса](/files/abdb728e7710d7653f03d8fea08c07aebb77e216)

На приведенной выше иллюстрации мы ясно видим, что список токенов аргумента содержит `$` как символьный токен, основанный на коде категории 3.

Как мы видели в Частях 1–3, символьные токены создаются с использованием значений кода категории *действующих в момент, когда символ считывается*— то есть в момент, когда создается список токенов аргумента (преобразуется в токены). В момент, когда аргументы токенизируются, `\docat` макрос еще не был выполнен, поэтому изменение кода категории, которое мы поместили в вызов макроса (``\catcode`\$=11``) *не* не влияет на коды категории, используемые для генерации токенов аргумента.

Как только TeX сгенерировал список токенов, представляющий аргумент `$90`, эти три символьных токена подаются в фактический макрос `<текст замены>`. Однако это приводит к тому, что `$` подается как *символьным токеном* созданный с использованием кода категории 3: «математика включена», и мы видели, что как только символьный токен сформирован, прикрепленный код категории становится постоянным.  `$` равно *не* подается в макрос *как символ*, а как *символьным токеном* основанный на `$` том, что ему присвоен код категории 3.

### Выполнение \docat: расширение макроса

TeX называет процесс «выполнения» макроса *расширением макроса*; этот термин, по мнению автора, немного сбивает с толку, но это общепринятая терминология, так что мы продолжим ее использовать.

#### Настоящий смысл расширения макроса

После того как TeX обнаружит `\docat` команду во вводе пользователя, он просматривает аргументы и генерирует список токенов для ее аргумента(`$90`). Чтобы выполнить (расширить) макрос, TeX отворачивается от входного файла пользователя и начинает читать токены, содержащиеся `\docat`его `<текст замены>` в списке токенов, хранящемся в памяти TeX.

По мере того как TeX обрабатывает `\docat`определение, он увидит и выполнит последовательность токенов, изначально использованную для определения макроса (`**catcode**`, ``**`**``, `**\$**`, `**=**`, `**1**`, `**1**`, `**#1**`).

Следующая иллюстрация показывает процесс расширения `\docat` макроса: TeX перестает получать токены из входного файла и начинает читать токены из `<текст замены>` раздела `\docat` определения макроса, хранящегося в памяти. TeX продолжает выполнять эти заранее подготовленные токены, пока не увидит **токен выходного параметра** , который предписывает TeX прочитать («вставить») и «выполнить» токены аргумента в этом месте. В нашем примере это три символьных токена, представляющие `$90` и это приводит к ошибке, потому что заранее подготовленный символьный токен для `$` имеет код категории 3. Поскольку мы имеем дело с символьными токенами, а не с символами, `$` не затрагивается предыдущим изменением кода категории, вызванным токенами в ``\catcode`\$=11``.

![Показ процесса расширения макроса \docat](/files/250983c9f25cd6669aa180311739dea46fd56734)

После того как TeX обработает токены, представляющие ``\catcode`\$=11``, изменение кода категории для `$` теперь вступит в силу. Затем TeX встречает «специальный токен» под названием **токен выходного параметра** , который сообщает TeX, чтобы **вставляют** список токенов для аргумента. Однако этот список токенов состоит из трех символьных *токены*, первым из которых является токен для `$` с присвоенным ему кодом категории 3 («математика включена»): предыдущее изменение кода категории внутри макроса не может повлиять на этот символьный токен, поэтому TeX рассматривает этот токен как сигнал начать математическую обработку, из-за чего макрос терпит неудачу.

### Можно ли исправить макрос \docat?

Из приведенного выше обсуждения ясно, что любые символы, появляющиеся в аргументах макроса, токенизируются с использованием кодов категории, действующих в момент токенизации, — а в нашем примере это *всегда* до того, как `<текст замены>` из `\docat` макрос фактически выполнится. Итак, как мы можем обеспечить изменение кодов категории у аргументов макроса?

Один из способов — изменить `\docat` так, чтобы он стал макросом без параметров, который только выполняет изменение кода категории — у него нет аргументов, которые нужно токенизировать. Затем мы используем второй макрос, `\getarg`, который принимает один параметр, и организуем так, чтобы его аргумент токенизировался, когда для `$` действует соответствующий код категории.

```
\begin{document}
\def\docat{\catcode`\$=11 \getarg} % Без параметров, вызывает второй макрос \getarg
\def\getarg#1{#1} %1 параметр, аргумент которого будет токенизирован
Теперь вы можете запустить это так, и это будет работать:

Я заплатил \docat{$90} за ту книгу.
\end{document}
```

Когда мы используем нашу новую версию `\docat` (например так `\docat{$90}`) она *кажется* будто `$90` по-прежнему используется как аргумент для `\docat` макроса. Однако, как обсуждалось выше, когда TeX обнаруживает `\docat` во входных данных, он проверяет, есть ли у него какие-либо аргументы: теперь их нет, поэтому TeX продолжает выполнять (расширять) его. Расширение `\docat` представляет собой последовательность токенов `**catcode**`, ``**`**``, `**\$**`, `**=**`, `**1**`, `**1**`, `**пробел**`, `**getarg**` и это происходит *до* когда TeX начинает читать (токенизировать) следующие символы, содержащиеся во входном файле, — то есть группу `{$90}`. Помните, что когда TeX расширяет макрос, он получает свой следующий ввод, читая токены, содержащиеся в списке токенов определения этого макроса; то есть из его `<текст замены>` раздела, хранимого в памяти.

TeX обработает и выполнит расширение `\docat` и обнаружит *токен* **`getarg`**, распознавая его как токен, представляющий команду, принимающую параметры. На этом этапе TeX просканирует входной файл в поисках **`getarg`**&#x430;ргумента: символов: `{$90}`. Как обычно, они токенизируются, но поскольку TeX прочитал и обработал расширение `\docat`, символы `$90` токенизируются, когда код категории `$` был изменен на 11. Определение (`<текст замены>`) `\getarg` просто `#1` что означает «наберите предоставленный аргумент», и именно это происходит, в результате чего получается `$` сгенерированный символьный токен с кодом категории 11 и безопасно набранный.

## Заключительные замечания: история в узлах

Последовательность событий, возникающая при переписывании `\docat` с использованием макроса `\getarg` содержится в следующей аннотированной диаграмме списка узлов, которая показывает процесс расширения макроса `\docat`. Читатели, желающие внимательно изучить эту диаграмму, могут скачать изображение в виде [PDF](https://assets.ctfassets.net/nrgyaltdicpt/7MOBdavza4WxAEQGISEEht/cabe5097d9063a6415bc553cd38237e6/newdocatexpansion.pdf) или [SVG](https://images.ctfassets.net/nrgyaltdicpt/2FIqqVRXjakdAzTpZsV0m9/10ccc1353741d42d3df18f1692d8aa84/newdocatexpansion--plain.svg) файла для офлайн-использования.

![Показ процесса расширения измененного макроса \docat и макроса \getarg](/files/e77de8f49a9ec890c1e35f17c506d22050cb8244)

[Часть 1](/latex/ru/drugie-temy/19-how-tex-macros-actually-work-part-1.md) [Часть 2](/latex/ru/drugie-temy/20-how-tex-macros-actually-work-part-2.md) [Часть 3](/latex/ru/drugie-temy/21-how-tex-macros-actually-work-part-3.md) [Часть 4](/latex/ru/drugie-temy/22-how-tex-macros-actually-work-part-4.md) [Часть 5](/latex/ru/drugie-temy/23-how-tex-macros-actually-work-part-5.md) [Часть 6](/latex/ru/drugie-temy/24-how-tex-macros-actually-work-part-6.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://ayakaleaf-pro.ayaka.space/latex/ru/drugie-temy/24-how-tex-macros-actually-work-part-6.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
