> For the complete documentation index, see [llms.txt](https://overleaf-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://overleaf-pro.ayaka.space/latex/ru/podrobnye-stati/09-an-introduction-to-luatex-part-2-understanding-directlua.md).

# Введение в LuaTeX (часть 2): понимание \\\directlua

## Цель этой статьи

В первой части этой статьи, [Введение в LuaTeX (часть 1): что это такое — и почему он так сильно отличается?](/latex/ru/podrobnye-stati/07-an-introduction-to-luatex-part-1-what-is-it-and-what-makes-it-so-different.md), мы кратко рассмотрели LuaTeX как чрезвычайно универсальный движок TeX: сложную, программируемую систему верстки, которая предоставляет широкий набор инструментов для создания решений по инженерии и выпуску документов.

В этом заключительном выпуске мы подробно рассмотрим самый важный компонент набора инструментов LuaTeX: `\directlua` команду, которая предоставляет «шлюз» к программному управлению версткой в LuaTeX через язык сценариев Lua.

Однако полноценное использование LuaTeX через `\directlua` требует некоторых базовых знаний по нескольким темам TeX: токены TeX, списки токенов и механизм раскрытия. Цель этой статьи — исследовать и объяснить эти фундаментальные понятия TeX: соединяя воедино процессы, связанные с TeX, лежащие в основе `\directlua` чтобы выработать понимание того, как он работает, и дать основу, на которой можно строить собственные решения по верстке с использованием LuaTeX.

В этой статье приводится множество коротких примеров, демонстрирующих и поясняющих ключевые аспекты `\directlua`его поведения, намеренно избегая чрезмерно сложного кода в пользу коротких фрагментов. При необходимости примеры используют базовый (raw/plain) TeX — хотя большинство людей используют и предпочитают LaTeX (макросы), базовые команды TeX обладают преимуществом простоты.

## Введение в Lua в LuaTeX

[Lua](https://www.lua.org/about.html) — это язык сценариев, чей [исходный код](https://www.lua.org/download.html) чрезвычайно портируем и легко встраивается в программные приложения, позволяя разработчикам добавлять возможности сценариев в свои программы. Lua встроен во [многие приложения](https://en.wikipedia.org/wiki/List_of_applications_using_Lua) и является популярным выбором в индустрии программных игр — возможно, самый известный пример — это [World of Warcraft](https://wowwiki.fandom.com/wiki/Lua_functions).

LuaTeX, как следует из названия, — это движок TeX, в который встроен язык сценариев Lua, предоставляющий пользователям возможность управлять поведением верстки LuaTeX, включая Lua-программы (скрипты) в свои документы. Помимо прямого управления LuaTeX, пользователи могут использовать Lua просто как весьма мощный язык программирования для выполнения задач, которые могут оказаться чрезвычайно трудными при использовании языка TeX — языка, который по любым справедливым меркам непросто изучить и освоить. Благодаря добавлению и интеграции Lua LuaTeX становится очень универсальным и мощным движком TeX, который напрямую поддерживает два языка программирования.

### Использование Lua и TeX в вашем документе: вводите \directlua

Lua и TeX — два *очень разные* языка программирования: Lua гораздо ближе к тому, что большинство людей считает языком программирования, но TeX с его кодами категорий, токенами, макросами и механизмом раскрытия очень далек от опыта/ожиданий большинства людей относительно языка, на котором пишут программы. Однако, как показала история, язык TeX выдержал испытание временем, потому что он хорош в том, для чего был создан: в управлении версткой, даже если способ его работы несколько архаичен.

Чтобы решить задачу смешивания языков Lua и TeX в одном документе TeX, разработчики LuaTeX ввели новую команду под названием `\directlua` которая является путем к использованию Lua — как самостоятельного языка программирования сам по себе и для управления поведением верстки LuaTeX.

Это `\directlua` команда позволяет пользователям встраивать Lua-код в свои документы TeX; затем этот код передается встроенному интерпретатору языка Lua в LuaTeX. Однако, `\directlua` также позволяет вам *сочетать* код Lua и (La)TeX вместе, внутри той же `\directlua` команды — хотя это вносит дополнительные сложности из-за фундаментальных различий между языками программирования на базе Lua и TeX. Ключевая задача при использовании сочетания кода (La)TeX и Lua состоит в том, чтобы обеспечить мирное сосуществование этих двух языков, не позволяя им «вставать друг у друга на пути».

`\directlua` лучше всего подходит для использования с более короткими фрагментами Lua-кода прямо в документе, но при желании его можно использовать и с более объемными Lua-программами. Как правило, более крупные Lua-программы и библиотеки кода Lua сохраняются во внешних файлах, которые можно загрузить, используя функцию Lua `dofile()` внутри `\directlua` команды. С точки зрения обработки TeX, значительным преимуществом использования внешних файлов Lua-кода является избежание осложнений, возникающих из-за механизма кодов категорий TeX — темы, полностью рассматриваемой в этой статье.

### Более формальное описание \directlua

Это [Справочное руководство LuaTeX](http://www.pragma-ade.com/general/manuals/luatex.pdf) описывает `\directlua` следующим образом (слегка изменено):

> Чтобы объединить Lua-код с входными данными TeX, нужны несколько новых примитивов. Примитив `\directlua` используется для немедленного выполнения Lua-кода. Базовый синтаксис: `\directlua{⟨code⟩}`. `код` полностью разворачивается, а затем передается интерпретатору Lua. После чтения и применения раскрытия к `код`, получившийся список токенов преобразуется в строку, как если бы он был выведен с помощью `\the\toks`.

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

## Понимание \directlua: какие темы мы рассмотрим?

В этой статье мы внимательнее рассмотрим некоторые ключевые базовые темы и приведем ряд примеров, призванных показать, как `\directlua` работает и где (или почему) нужно быть осторожным при сочетании TeX и Lua в вашем `код`.

Мы достаточно подробно рассмотрим следующие темы, чтобы заложить основу для понимания `\directlua` и его «предобработки» кода, который вы используете внутри него:

* коды категорий и токены TeX: преобразование текста в токены и токенов в текст;
* процесс раскрытия в TeX (и предотвращение раскрытия);
* механизмы/последовательности экранирования Lua для символов и строк;
* использование комментариев в стиле Lua;
* краткое введение в Lua API LuaTeX.

Если вы поймете, как движки TeX создают и используют токены, и осознаете механизм раскрытия TeX, то у вас будет основа, необходимая для раскрытия невероятной универсальности LuaTeX’s `\directlua` команды.

## Основа: от текста к токенам и от токенов к тексту

Overleaf опубликовал несколько статей, которые подробно рассматривают токены TeX и связанные понятия, поэтому мы не будем повторять здесь весь этот материал; вместо этого мы обрисуем те области/темы, которые важны для лучшего понимания `\directlua`.

Вот список ранее опубликованных статей, которые могут быть интересны:

* [Что такое токен TeX?](/latex/ru/podrobnye-stati/53-what-is-a-tex-token.md)
* [Что такое список токенов TeX?](/latex/ru/podrobnye-stati/54-what-is-a-tex-token-list.md)
* [Как работает \expandafter: введение в токены TeX](/latex/ru/podrobnye-stati/19-how-does-expandafter-work-an-introduction-to-tex-tokens.md)
* [Серия из шести частей: как на самом деле работают макросы TeX?](/latex/ru/drugie-temy/01-a-six-part-series-how-do-tex-macros-actually-work.md)

### Понимание символьных токенов

Любой символ, который движок TeX может прочитать из текстового файла, представлен двумя числовыми значениями:

* его *код символа* (значение ASCII или, сегодня, его кодовая точка Unicode);
* второе, ориентированное на TeX, значение, называемое его *кодом категории*.

Читателям, которые хотели бы узнать больше о кодах категорий, может быть интересно прочитать это введение, опубликованное Overleaf: [Так с чего же начать? С кодов категорий](/latex/ru/drugie-temy/19-how-tex-macros-actually-work-part-1.md#so2c-where-do-we-start3f-with-category-codes).

Например, если движок TeX читает символ `Один` он будет иметь доступ к двум кусочкам информации: `Один`код символа (65) и его код категории (обычно 11). Как только TeX получил этот символ на входе, `Один`, его код категории не изменится, но пользовательские макросы могут менять коды категорий, что может повлиять на любой *последующий* символ `Один` который *еще не был прочитан* TeX. Следовательно, TeX должен записать, что *этот* символ `Один`, *только что прочитанный*, имеет код категории 11. Для этого TeX использует пару целых чисел (65,11) для вычисления другого целочисленного значения, которое он называет *символьным токеном*. Вычисляя это значение токена, которое передается во внутреннюю обработку TeX, данный `Один` и его код категории *связываются воедино*; фактически, этот символьный токен *заключает в себе* данные, которые TeX должен знать об этом символе для использования в любых последующих операциях верстки глубже внутри движка TeX.

#### Как вычисляются символьные токены?

Во-первых, нужно помнить, что движки TeX используют код категории 13 для создания так называемых *активных символов*: любой символ с кодом категории 13 ведет себя как мини-макрос; следовательно, и, как мы увидим ниже, токены для активных символов вычисляются иначе, чем для обычных символов с другими кодами категорий, такими как 10, 11 или 12.

Для *неактивных* символов:

* старые 8-битные движки (TeX Кнута, e-TeX, pdfTeX) вычисляют символьные токены для *неактивных* символов, используя

$$\text{(non-active) character token} = (256 \times \text{category code}) + (\text{ASCII character code})$$

* для LuaTeX, которому приходится иметь дело со значениями символов Unicode, вычисление для *неактивных* символов аналогично, но дает гораздо большие целочисленные значения:

$$\text{(non-active) character token} = (2^{21} \times \text{category code}) + (\text{Unicode value})$$

Возвращаясь к нашему предыдущему примеру с буквой A с кодом категории 11, LuaTeX вычислил бы значение символьного токена $$2^{21} \times 11 + 65 = 23068737$$. После вычисления это значение символьного токена *связывает* этот конкретный символ A со значением кода категории 11. Пользовательские макросы могут изменить код категории для любого последующего символа A, но у этого символа код категории уже зафиксирован путем преобразования его в токен для использования по мере прохождения через внутренние механизмы LuaTeX. LuaTeX сохранил, или заключил в оболочку, предполагаемое значение этого символа, определенное в момент его чтения.

Движки TeX используют всего [16 различных кодов категорий](/latex/ru/drugie-temy/43-table-of-tex-category-codes.md) и *любой* из этих кодов категорий может быть назначен с помощью команды `\catcode` любому *любой* символу, который движок TeX способен прочитать. Изменения кодов категорий используются для изменения способа, которым движки TeX обрабатывают определенные символы во входных данных, позволяя пользователям TeX писать макросы, которые создают особые результаты верстки или особое поведение.

**Активные символы**

Как отмечалось, движки TeX используют код категории 13, чтобы придать символу «особое значение», делая его так называемым *активным символом* который ведет себя как мини-макрос: не требуется начальный `\` — изолированного символа, благодаря его коду категории, достаточно, чтобы запустить его макроподобное поведение.

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

* для старых 8-битных движков (TeX Кнута, e-TeX, pdfTeX) токены для активных символов вычисляются по:

1. вычислить промежуточное значение, называемое $$\text{curcs}$$ (**тек**ущей **c**управляющей **последовательности**) где $$\text{curcs} = \text{character code} + 1$$3. вычислить значение токена, где $$\text{active character token} = \text{curcs} + \text{4095}$$

* для LuaTeX вычисление немного сложнее, потому что ему приходится иметь дело со всем диапазоном символов Unicode, любой из которых может быть сделан активным:

1. вычислить промежуточное целочисленное значение $$\text{curcs}$$ применив так называемую *хэш-функцию* к значению кодовой точки Unicode активного символа, представленному в UTF-8: $$\text{curcs}=\texttt{hashfunction}\text{(UTF-8 text for Unicode value of active character)}$$3. вычислить целочисленное значение токена: $$\text{active character token} = \text{curcs} + 2^{29} - 1$$

**Примеры**

* 8-битные движки: вычисление токена для активного символа `~` (код символа 126) дает $$\text{curcs} = 126 + 1 = 127$$, в результате получается значение токена $$4095 + 127 = 4222$$.
* LuaTeX: вычисление токена для активного символа `~` дает результат $$\text{curcs}=3186$$ что дает значение токена $$3186 + 2^{29} - 1 = 536874097$$. Токены LuaTeX используют гораздо большие целочисленные значения!

### Понимание командных токенов

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

используется для обозначения начала команды, но это всего лишь соглашение — на самом деле вместо него можно было бы использовать любой символ с кодом категории 0 (символом экранирования). *управляющие слова* и *управляющие символы*:

* **управляющие слова**: команды, составленные из одного или нескольких символов, имеющих код категории 11;
* **управляющие символы**: однобуквенные команды, у которых код категории этого символа *не* 11: такие как `\$`, `\#` или `\\`.

**Примечание**: Примитивы TeX `\chardef`, `\mathchardef`, `\countdef`, `\dimendef`, `\skipdef`, `\muskipdef` и `\toksdef` также используются для определения управляющих последовательностей, но, в отличие от обычных определений макросов, получившиеся управляющие последовательности (управляющие слова или управляющие символы) *не являются расширяемыми*—мы рассмотрим их подробнее ниже.

#### Как вычисляются командные токены?

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

Вычисления, используемые 8-битными движками для создания целых значений командных токенов, можно найти в этой [статье Overleaf](/latex/ru/podrobnye-stati/19-how-does-expandafter-work-an-introduction-to-tex-tokens.md#how-tex-calculates-token-values). Здесь мы кратко изложим ключевые шаги в вычислении командных токенов для LuaTeX — которые немного отличаются, потому что LuaTeX должен обрабатывать значения кодов символов Unicode, которые могут быть значительно больше 8-битных значений; однако вычисления LuaTeX следуют тем же общим принципам, что и более старые 8-битные движки.

После обнаружения входящей команды движки TeX, включая LuaTeX, игнорируют начальный `\` символ: он не используется в вычислениях значений командных токенов, а лишь служит «переключателем», сообщающим движку TeX, что ему нужно обработать команду. Значение командного токена вычисляется по последовательности из (одного или нескольких) символов, присутствующих в имени команды — LuaTeX вычисляет командные токены для управляющих символов и управляющих слов по одному и тому же алгоритму:

1. вычислить промежуточное целочисленное значение $$\text{curcs}$$ применив так называемую [хэш-функцию](https://en.wikipedia.org/wiki/Hash_function) к строке Unicode UTF-8 из символов, содержащихся в имени команды: $$\text{curcs}=\texttt{hashfunction}\text{(Unicode UTF-8 string of characters in command name)}$$3. вычислить значение командного токена, где $$\text{command token} = \text{curcs} + 2^{29} - 1$$

**Примеры**

* для `\\` команды (управляющего символа) LuaTeX вычисляет $$\text{curcs}=94$$, в результате чего получается значение токена для `\\` для $$94 + 2^{29} - 1 = 536871005$$.
* для `\vskip` примитивной команды (управляющего слова) LuaTeX вычисляет $$\text{curcs}=3560$$, в результате чего получается значение токена для `\vskip` для $$3560 + 2^{29} -1 = 536874471$$.
* для пользовательского макроса `\mynewmacro` (управляющего слова) LuaTeX вычисляет $$\text{curcs} = 2971$$, в результате чего получается значение токена для `\mynewmacro` для $$2971 + 2^{29} -1 = 536873882$$.

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

### Как движок TeX определяет тип токена (команда или символ)

Имея определенное целочисленное значение токена, $$T$$, движок TeX может легко определить, представляет ли $$T$$ команду или символ, проверив, превышает ли $$T$$ определенный $$\text{threshold value}$$—это $$\text{threshold value}$$ зависит от движка TeX. Если $$T \geq \text{threshold value}$$ затем $$T$$ —это командный токен, иначе $$T$$ — это символьный токен. Значение $$\text{threshold value}$$ равно $$4095$$ для 8-битных движков и $$2^{29}-1$$ (536,870,911) для LuaTeX. Кнут разработал методы, используемые в формулах вычисления токенов, так, чтобы его движок TeX и все последующие движки, основанные на его коде/архитектуре, могли быстро и легко проверять значения токенов.

## Токены могут быть разобраны на части (и преобразованы обратно в текст)

Токены (целые числа) — это механизм, посредством которого движок TeX «заключает в оболочку» все, что ему нужно записать об элементе входа (символе или команде). Однако бывают случаи, когда движку TeX нужно обратить процесс токенизации вспять — чтобы выяснить, что было изначально прочитано для получения этого значения токена — отдельный символ или последовательность из одного или нескольких символов, образующих имя команды:

* **для символьных токенов**: любой символьный токен можно разделить на две составляющие части: код символа и соответствующий код категории, присвоенный этому символу *в момент его первоначального чтения*. Как и все движки TeX, LuaTeX не изменит это исходное назначение кода категории, но будет использовать его в дальнейших внутренних операциях обработки.
* **для командных токенов:** Они немного подробнее, но если посмотреть на вычисление командных токенов в LuaTeX, включая токены для активных символов, видно, что они следуют шаблону: $$\text{command token} = \text{curcs} + 2^{29} -1$$

где $$\text{curcs}$$ вычисляется в зависимости от типа создаваемого командного токена: активный символ, управляющий символ или управляющее слово. Переменная $$\text{curcs}$$ является *чрезвычайно* важным компонентом внутренних операций движка TeX: имея любое значение командного токена (целое число), LuaTeX может очень легко извлечь значение $$\text{curcs}$$ из этого командного токена с помощью $$\text{curcs} = \text{token value} - (2^{29} -1)$$.

### Почему $$\text{curcs}$$ так важно?

Внутренняя переменная TeX $$\text{curcs}$$ (**тек**ущей **c**управляющей **последовательности**последовательность) является жизненно важным компонентом внутренних операций движка TeX «под капотом». Хотя вы не будете и не можете использовать ее или обращаться к ней напрямую в своем коде, $$\text{curcs}$$ играет ключевую роль, потому что движки TeX используют текущее значение $$\text{curcs}$$ как индекс во внутренних таблицах, которые хранят данные обо всех командах, известных движку на данный момент. В этих таблицах хранится информация о текущем значении команды: что она делает или что она представляет, а также записывается последовательность символов, изначально использованная для вычисления этого $$\text{curcs}$$ значения. Извлекая значение $$\text{curcs}$$ из командного токена, движок TeX может определить имя, то есть человекочитаемый текст, соответствующий любому (командному) токену, что позволяет ему выполнять преобразования из токенов в текст, являющиеся ключевым аспектом работы `\directlua`.

### Преобразование целочисленных токенов обратно в символы или последовательности символов (имена команд)

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

* вывод ошибок или предупреждений на экран или в `.log` файле;
* вывод кода TeX/LaTeX в текстовый файл через команду `\write` ;
* при преобразовании последовательности токенов в текст внутри `\directlua` (как мы вскоре увидим!)

#### Преобразование символьных токенов в текст

Как отмечалось, токены для неактивных символов вычисляются с использованием кода категории входного символа и кода символа (значения Unicode). LuaTeX использует формулу:

$$\text{character token} = (2^{21} \times \text{category code}) + (\text{Unicode value})$$

Простая программная задача — разделить целочисленное $$\text{character token}$$ значение, чтобы получить его составляющий код символа ($$\text{Unicode value}$$) и $$\text{category code}$$.

#### Преобразование командных токенов в текст

Все движки TeX хранят имя (последовательность символов) каждой команды, о которой они «знают»: будь то пользовательский макрос или встроенный примитив — хранение имен примитивов происходит при запуске движка TeX, задолго до того, как он начнет обрабатывать ваш код. Для пользовательских команд (макросов) имя этого макроса (без начального `\`) хранится как часть процессов определения макроса внутри движков TeX.

Когда движку TeX нужно получить доступ к человекочитаемому тексту, из которого изначально было вычислено целочисленное значение командного токена, или вывести его, он сначала определит $$\text{curcs}$$ значение этого токена; в LuaTeX, $$\text{curcs} = \text{token} - (2^{29} -1$$). Используя значение $$\text{curcs}$$ извлеченное из командного токена, движок TeX может получить доступ к внутренней структуре данных, называемой *пулом строк* чтобы определить последовательность человекочитаемых символов, изначально использованную для вычисления этого конкретного значения $$\text{curcs}$$ и, следовательно, соответствующего командного токена.

Как мы увидим, эти действия по обработке токенов — преобразование последовательностей символов в целочисленные значения токенов и преобразование целочисленных значений токенов обратно в последовательности символов («де-токенизация») — являются *фундаментальными механизмами* используемыми внутри `\directlua`.

## Списки токенов

По мере того как движок TeX читает входные данные, генерирует и обрабатывает символьные и командные токены, он может встретить определенные команды, которые приказывают движку (временно) перестать передавать токены дальше для последующей обработки, а вместо этого сохранить их для использования позже. Самый распространенный пример — определение макроса с помощью одной из команд определения макросов `\def`, `\edef`, `\gdef` или `\xdef`— команды LaTeX, такие как `\newcommand` — это макросы, которые предоставляют дополнительную функциональность, построенную вокруг низкоуровневых примитивов, которые в конечном итоге выполняют сам процесс определения макроса. Макрос можно рассматривать как имя, данное определенному списку сохраненных токенов: списку токенов.

Движки TeX *широко* используют списки токенов, особенно [временные внутренние списки](/latex/ru/podrobnye-stati/21-how-does-expandafter-work-tex-uses-temporary-token-lists.md) используемые для внутренних целей обработки. Каждый движок TeX также предоставляет команды пользовательского уровня для создания списков токенов, которые сохраняются на случай, если они понадобятся пользователю или самому движку TeX. Количество команд создания списков токенов (встроенных примитивов) варьируется в зависимости от движка TeX, но у всех есть общий минимальный набор, поддерживаемый каждым движком, такой как примитив `\toks` .

На практике список токенов — это просто сохраненная последовательность целочисленных значений:

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

Движки TeX хранят списки токенов, используя структуру данных, называемую [связным списком](https://en.wikipedia.org/wiki/Linked_list) (односвязным). Читателям, желающим узнать больше о списках токенов, предлагается прочитать статью Overleaf [Что такое список токенов TeX?](/latex/ru/podrobnye-stati/54-what-is-a-tex-token-list.md) которая использует аналогию для построения концепций/идей, лежащих в основе списка токенов. Подробное исследование списков токенов TeX и того, как они используются при обработке макросов, можно найти в серии статей Overleaf [Как на самом деле работают макросы TeX?](/latex/ru/drugie-temy/01-a-six-part-series-how-do-tex-macros-actually-work.md)

#### Список токенов в графической форме

Следующая иллюстрация показывает список токенов, сгенерированный LuaTeX, с соответствующими значениями токенов, полученными из следующего входа

`Hi, \TeX! \hskip 5bp`

Например, если мы определим `\mymacro` как `\def\mymacro{Hi, \TeX! \hskip 5bp}` определение `\mymacro` будет храниться в памяти с использованием такого списка токенов:

![](/files/a29f4730f4a09ce086db6b71d077a9b389bced16)

Список токенов — это последовательность связанных элементов, называемых *узлами*, так называется небольшой пакет памяти LuaTeX, выделенный для хранения каждого элемента списка (как отдельные звенья в цепи). Каждый узел содержит целочисленное значение токена и адрес памяти *следующего* узла в цепочке, образуя структуру данных, называемую [связным списком](https://en.wikipedia.org/wiki/Linked_list). Последний узел указывает на конец списка, используя специальное «нулевое значение» для следующего узла — потому что его не существует.

**Примечания:**

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

Ниже приведена таблица значений токенов, содержащихся в показанном выше списке токенов:

|                     |                    |                                                                                 |                     |
| ------------------- | ------------------ | ------------------------------------------------------------------------------- | ------------------- |
| **Входной элемент** | **Тип ввода**      | <p><strong>Код категории</strong><br><br><strong>(если это символ)</strong></p> | **Значение токена** |
| H                   | символ             | 11                                                                              | 23068744            |
| i                   | символ             | 11                                                                              | 23068777            |
| ,                   | символ             | 12                                                                              | 25165868            |
|                     | символ             | 10                                                                              | 20971552            |
| \TeX                | команда (макрос)   |                                                                                 | 536871539           |
| !                   | символ             | 12                                                                              | 25165857            |
|                     | символ             | 10                                                                              | 20971552            |
| \hskip              | команда (примитив) |                                                                                 | 536874247           |
| 5                   | символ             | 12                                                                              | 25165877            |
| b                   | символ             | 11                                                                              | 23068770            |
| p                   | символ             | 11                                                                              | 23068784            |

**Примечание:** В нашем исходном входном тексте после `\hskip` команды есть a, но в списке токенов нет токена, представляющего этот символ. Этот символ был поглощён процессом сканирования (чтения) входа LuaTeX, поскольку он использовался для завершения поиска LuaTeX символов, составляющих `\hskip` команды.

## Как на самом деле работает \directlua

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

Как было отмечено, `\directlua{⟨code⟩}` можно попросить обработать `код` который содержит и код Lua, и код TeX/LaTeX, но встроенный интерпретатор Lua в LuaTeX не понимает TeX или LaTeX: так как же это работает? Как возможно, чтобы `код` содержал инструкции TeX/LaTeX, не полностью запутывая интерпретатор Lua командами, которые он не понимает? Например, следующая `\directlua` команда использует только макросы TeX, но она работает:

```
\def\aa{tex}
\def\bb{.}
\def\cc{print}
\def\dd{("Hello")}
\directlua{
   \aa\bb\cc\dd
}
```

Этот `\directlua` команда приводит к тому, что LuaTeX набирает `Hello` но почему и как это работает, ведь язык Lua не понимает макросы TeX?

Ответ содержится в предыдущем описании, которое мы заимствовали у [Справочное руководство LuaTeX](http://www.pragma-ade.com/general/manuals/luatex.pdf) но мы можем считать, что `\directlua{⟨code⟩}` работает за счёт того, что LuaTeX сначала «предобрабатывает» `код` прежде чем что-либо передаётся интерпретатору Lua. Суть этой «предобработки» — то есть что она на самом деле означает и каковы её последствия для вашего `код`— это следующая тема, которую мы рассмотрим, чтобы помочь читателям, заинтересованным в использовании мощности и гибкости `\directlua`.

### Как LuaTeX обрабатывает \directlua: первый взгляд

Чтобы расширить наше понимание `\directlua`«предобработки» можно начать со следующей упрощённой схемы, которая даёт общий обзор происходящего.  `код` передаваемый в `\directlua{⟨code⟩}` сначала преобразуется в токены с использованием процессов и вычислений, описанных выше; эта последовательность токенов хранится в списке токенов. После того как этот список токенов построен, каждый токен в нём преобразуется обратно в текстовое представление: текст, создаваемый каждым токеном — символьным токеном или токеном команды, — объединяется (конкатенируется), чтобы создать одну строку кода, передаваемую интерпретатору Lua для выполнения.

![](/files/5db8a5a3fee371f21aa904c92caf9098e5056b75)

Но подождите, какой смысл переходить от текста к токенам и затем тут же преобразовывать эти токены обратно в текст? Возможно, вас не удивит, что да, здесь есть дополнительный и крайне важный процесс, который мы не включили в эту схему: *расширение токенов*. Каждый токен, полученный из текста в вашем `код` подвергается своего рода «проверке», в ходе которой LuaTeX выполняет тест, чтобы определить, представляет ли этот токен команду, относящуюся к подмножеству, известному как *расширяемые команды*. Если да, LuaTeX отфильтровывает эту команду, *удаляя* её из вашего `код` и *заменяя её* результатом процесса, который движки TeX называют *расширение токенов*.

### Как LuaTeX обрабатывает \directlua: второй взгляд (на расширение)

Механизм расширения TeX — это ключевой компонент всех движков набора на базе TeX, поскольку в конечном счёте каждый из них является производным от (или основан на) исходном коде и дизайне TeX Кнута. Однако концепцию расширения трудно объяснить кратким, но доступным языком, потому что на практике расширение — это собирательный термин, используемый для описания одного процесса, который, однако, даёт целый диапазон результатов. Эти различающиеся результаты являются следствием несколько эклектичного набора команд, к которым можно применять расширение, поэтому можно считать, что у каждой расширяемой команды есть определённое «поведение при расширении».

В качестве *первого приближения* к пониманию расширения можно сказать, что расширение токена (команды) означает *удаляя* удаление этой команды (токена) из текущего входного потока TeX и *замену* её последовательностью токенов, полученной в результате выполнения этой конкретной расширяемой команды, — то есть замену исходного токена результатами/следствиями его расширения *поведение*Однако это начальное «определение» расширения — в терминах порождения новых токенов, которые TeX должен прочитать, — не совсем точно для всех расширяемых команд, но для начала оно вполне подходит.

Для простого примера: примитив TeX `\jobname` является расширяемой командой, а его *расширения* — это последовательность символьных токенов, представляющих имя основного входного файла TeX. Если TeX решает расширить `\jobname` команду (токен), она *удаляется* из текущего источника ввода TeX и *заменяется* последовательностью символьных токенов, которую она генерирует, — после чего TeX читает/обрабатывает её.

Внутри `\directlua`, после того как расширяемый токен обработан (удалён) и заменён новыми токенами, LuaTeX продолжит читать эти новые токены, которые он только что подставил, — но некоторые из новых токенов тоже могут быть расширяемыми. Поскольку `\directlua` выполняет так называемое *полное расширение*, LuaTeX будет читать эти новые токены и снова проходить процесс расширения, чтобы расширить (удалить) любые новые (расширяемые) токены — этот процесс расширения продолжается до тех пор, пока не останется ни одного расширяемого токена. Однако есть два важных исключения из этого правила «продолжать расширять», и оба мы обсудим ниже:

* с использованием конструкции `\the\toks`;
* намеренное предотвращение (подавление) расширения одного или нескольких выбранных токенов.

Как было отмечено, наше рабочее определение (первое приближение) понимания расширения не охватывает весь диапазон поведения при расширении, демонстрируемый подмножеством расширяемых команд. Например, некоторые расширяемые команды не генерируют токены так, как это делает `\jobname` , но они могут:

* «фильтровать» токены из входа: условные команды движка TeX (`\if`, `\ifcat`, `\ifnum`, `\ifdim`, `\ifodd`, `\ifvmode`, …) являются расширяемыми. Их поведение при расширении — это своего рода «фильтрация токенов» — условные конструкции можно использовать в `\directlua`.
* «перетасовывать» токены во входе:  [`\expandafter` команда](/latex/ru/podrobnye-stati/03-a-six-part-article-series-on-expandafter-tex-tokens-and-expansion.md) является расширяемой и изменяет порядок, в котором расширяются два токена.
* предотвращать расширение: расширяемые команды `\noexpand` и `\unexpanded` подавляют расширение токенов команд во входе.
* преобразуют последовательности символов во входе в токены команд: `\csname … \endcsname.`
* преобразуют внутренние величины в последовательность символьных токенов: `\number` и `\the` являются расширяемыми командами, которые генерируют последовательность символьных токенов, представляющих значение внутренней величины.
* преобразуют токены команд в символьные токены: `\string` и `\detokenize` являются расширяемыми командами, которые преобразуют свои аргументы в последовательность символьных токенов с кодом категории 12. Обратите внимание, что `\detokenize` отличается от `\string`: `\detokenize` может обрабатывать несколько токенов и вводит символ пробела с кодом категории 10 после обработки токенов команд, созданных из *управляющие слова*. По сути, `\detokenize` добавляет завершающий символ пробела после имени команды — некоторые примеры мы увидим позже в статье.

#### Уточнение нашего «определения» расширения

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

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

Ещё одна трудность при объяснении/понимании расширения, а возможно и настоящая проблема, заключается в том, чтобы точно знать *когда* движок TeX действительно будет или не будет выполнять процесс расширения. Это большая и сложная тема, поскольку расширение глубоко встроено во внутреннюю работу движков TeX: у нас нет места, чтобы подробно рассматривать это, за исключением использования расширения в `\directlua`.

### Как LuaTeX обрабатывает \directlua: заключительный взгляд

Следующая схема суммирует `\directlua` действия предобработки, которые происходят внутри самого движка LuaTeX. На этой схеме мы также показываем две низкоуровневые (внутренние) функции LuaTeX, которые фактически выполняют работу: `scan_toks()` и `tokenlist_to_cstring()`. Эти функции написаны на языке C и находятся глубоко внутри исполняемого ПО LuaTeX: они являются частью внутреннего механизма LuaTeX и не *непосредственно* доступны вашему коду TeX/LaTeX.

![](/files/7117d7abe64daca65226c32c90b19c6ede63eb7b)

Следующее описание `\directlua ⟨code⟩`предобработки подводит итог схеме выше.

1. Последовательность символов в вашем ⟨code⟩ обрабатывается `scan_toks()`. Его цель — читать ваш ⟨code⟩ посимвольно, чтобы генерировать символьные токены и токены команд. Поскольку при этом создаются токены, код категории, присвоенный каждому символу в ⟨code⟩ в момент его чтения, чрезвычайно важен.
2. Во время `scan_toks()`обработки (генерации) токенов любая расширяемая команда (токен) расширяется *если только* подавляется с помощью таких команд, как `\protected` (определения макросов), `\noexpand`, `\unexpanded` и т. д. Активные символы (код категории 13) тоже расширяются (если это не предотвращено).
3. Поток токенов, создаваемый `scan_toks()` собирается в один длинный список токенов — токены, присутствующие в этом списке, включают те, которые были получены в результате расширения, применённого к расширяемым командам (таким как макросы), присутствующим в вашем `код`Также обратите внимание, что `scan_toks()` *не* не запускают и не вызывают выполнение какого-либо токена, представляющего нерасширяемую команду: такие нерасширяемые токены просто пропускаются дальше, чтобы быть включёнными в строящийся список токенов.
4. Когда список токенов завершён и вся деятельность по расширению завершена, этот список токенов обрабатывается другой функцией под названием `tokenlist_to_cstring()` которая преобразует каждый токен в конечном списке токенов обратно в его текстовое представление. Это генерирует строку текста, которая и является кодом Lua, передаваемым интерпретатору Lua. Для успешного выполнения эта строка должна содержать синтаксически корректный код Lua.
5. Обработка этого кода в Lua происходит в два шага:
6. Встроенный интерпретатор Lua в LuaTeX разбирает и «компилирует» код Lua, сгенерированный на предыдущих шагах. Если этот разбор/компиляция завершается неудачно, интерпретатор Lua выдаст ошибки (например, синтаксические ошибки) — эти ошибки могут привести к сбою запуска LuaTeX, если только вы не выбрали использование `--interaction=nonstopmode` в командной строке.
7. Если разбор/компиляция завершается успешно, интерпретатор Lua выполняет код, скомпилированный на шаге (5a).

По сути, `scan_toks()` функция — это ядро предобработки LuaTeX: её основная задача — расширить все расширяемые команды TeX/LaTeX, содержащиеся в тексте вашего `код` и построить список токенов из всего, что она обработала. Снова подчеркнём, что `scan_toks()` *не выполняет нерасширяемые команды* (токены): она просто *сохраняет* эти токены в строящийся ею список токенов. После завершения этот список токенов затем преобразуется *обратно в текстовое представление* с помощью `tokenlist_to_cstring()`— список токенов является концепцией только TeX и совершенно чужд интерпретатору Lua, поэтому возникает необходимость преобразовать его в текст, который станет кодом Lua для передачи интерпретатору Lua.

## Расширение как «интерфейс» языка программирования

Можно думать о `\directlua`процессе расширения как о механизме или интерфейсе для передачи данных/информации из «мира TeX» в «мир Lua»: способе, с помощью которого язык TeX сообщает данные языку Lua. Например, код TeX, такой как `\number\count75` может использоваться для передачи значения из «мира TeX», хранящегося в счётном регистре 75, в целочисленную переменную x в «мире Lua»:

```
\count75=1564 % Данные, существующие в «мире TeX»
\directlua{
   local x=\number\count75 \space % Передача данных TeX в «мир Lua»
   tex.print("x= "..x)
   local y = (2*x-65)/5
   tex.print(" и y = "..y)
}
```

Это генерирует код Lua

```
 local x=1564 tex.print("x= "..x) local y = (2*x-65)/5 tex.print(" и y = "..y)
```

**Примечание**: Мы добавили `<space>\space` после `\number\count75` чтобы между `1564` и `tex.print`— это здесь не строго необходимо, потому что Lua всё равно правильно разобрал бы код, если бы мы его опустили. Символ пробела, следующий сразу после `\count75` поглощается в процессе, который движки TeX используют для поиска числовых значений — здесь это значение передаётся в `\count`. `75` Символ пробела после `75` используется для завершения поиска LuaTeX последовательности цифр `\space` макрос расширяется, предоставляя символ пробела, необходимый для разделения текста `1564` и `tex.print`.

Используя приведённый выше код, LuaTeX наберёт

`x= 1564 and y = 612.6`

Здесь механизм «передачи данных» достигается с помощью `\number`: расширяемой команды, которая в данном случае предписывает TeX извлечь значение, хранящееся в `\count` регистре `75` и из этого значения (`1546`) генерировать серию символьных токенов, по одному символьному токену на каждую цифру, в результате чего получается последовательность токенов для цифр `1`, `5`, `6` и `4`. Эти 4 символьных токена включаются в основной список токенов, строящийся `\directlua` и затем впоследствии преобразуются обратно в их текстовое представление, когда список токенов преобразуется в текст. Безусловно, это очень окольный путь — от `\count75` значения регистра, хранящегося внутри LuaTeX, к цифрам, предназначенным для кода Lua, но в конечном счёте это работает.

**СОВЕТ:** Если вы хотите проверить результаты деятельности LuaTeX по расширению, можно написать такой код:

```
\directlua{
   local foo=[[local x=\number\count75
   tex.print("x= "..x)
   local y = (2*x-65)/5
   tex.print(" и y = "..y)]]
   print(foo)
}
```

В этом примере мы используем метод длинных скобок, чтобы создать строковую переменную `foo` назначение которой — хранить строку кода Lua, полученную в результате расширения всего, что находится между `[[` и `]]`. Эта строка выводится в консоль через вызов функции Lua `print(foo)`.

В Overleaf вы можете увидеть похожие результаты, записав содержимое `foo` в `.log` файл с помощью функции Lua LuaTeX `texio.write()`:

```
\directlua{
   local foo=[[local x=\number\count75
   tex.print("x= "..x)
   local y = (2*x-65)/5
   tex.print(" и y = "..y)]]
   texio.write(foo)
}
```

## Токены в списке токенов \directlua: нерасширяемые токены и нерасширенные токены

Мы отметили, что `\directlua{⟨code⟩}` выполняет *полное расширение* вашего `код`: он удаляет и расширяет все расширяемые команды, пока не останутся только нерасширяемые токены. Последовательность токенов, создаваемая `\directlua`обработкой (в `scan_toks()` функции) соединяется в список токенов, отдельные токены которого будут преобразованы обратно в текст для передачи в Lua.

Однако мы ещё не рассмотрели заключительную часть этой истории, поскольку нам нужно учесть два «класса» токенов команд, которые могут попасть в список токенов, строящийся внутри `\directlua`: мы будем называть их *токены сокращённых команд* и *нерасширенные* токены:

* **токены сокращённых команд**: Этот тип токена команды возникает из управляющих последовательностей, определённых с использованием одного из примитивов TeX `\chardef`, `\mathchardef`, `\countdef`, `\dimendef`, `\skipdef`, `\muskipdef` и `\toksdef`. Эти примитивные команды используются для определения управляющих последовательностей, представляющих числовое значение, — в результате такие управляющие последовательности *не* не являются расширяемыми.
* **нерасширенные токены**: Этот тип токена возникает из команд, которые обычно были бы расширены, но `\directlua` либо
* им было явно предписано *не* их расширять; например, подавление расширения командами `\noexpand` или `\unexpanded`— чуть позже мы объясним, как это делается;
* введены токены путём обработки последовательности `\the\toks` (подробнее об этом ниже).

### Две «группы» токенов в списке токенов \directlua

Основываясь на нашем обсуждении, можно сказать, что токены, содержащиеся в списке токенов, строящемся на первом этапе `\directlua`предобработки (в `scan_toks()` функции), делятся на две группы:

1. *изначально нерасширяемые* токены

* любой токен, представляющий неактивный *символ*;
* любой токен, представляющий нерасширяемый *примитив* *команда*;
* любой токен, представляющий *сокращённую команду* (они не являются расширяемыми, см. ниже).

3. *нерасширенные* токены:

* любой токен, представляющий расширяемую команду, расширение которой было *подавлено* (или предотвращено) во время `\directlua`предобработки.

#### Токены сокращённых команд: создание нерасширяемых команд

Как отмечалось, движки TeX предоставляют набор примитивов (встроенных команд), которые можно использовать для создания *нерасширяемых* управляющих последовательностей (обозначенных здесь как `⟨command⟩`). Эти примитивы имеют вид:

* `\chardef ⟨command⟩ = ⟨numeric value⟩`
* `\mathchardef ⟨command⟩ = ⟨numeric value⟩`
* `\countdef ⟨command⟩ = ⟨numeric value⟩`
* `\dimendef ⟨command⟩ = ⟨numeric value⟩`
* `\skipdef ⟨command⟩ = ⟨numeric value⟩`
* `\muskipdef ⟨command⟩ = ⟨numeric value⟩`
* `\toksdef ⟨command⟩ = ⟨numeric value⟩`

где `⟨numeric value⟩` — это некоторое целое значение, соответствующее каждой команде.

Здесь мы кратко рассмотрим использование `\chardef` чтобы продемонстрировать ключевую особенность этих примитивов — создание `⟨command⟩` которой не является расширяемой. Можно использовать ``\chardef\mydollar=`\$`` для создания управляющей последовательности `\mydollar` и использовать её для набора `$`:

`I paid \mydollar30.`

Это даст набор `I paid $30.` Управляющая последовательность `\mydollar` созданная с помощью `\chardef` не является расширяемой, как видно из следующего примера:

```
\chardef\mydollar=`\$
\directlua{
   local x =[[I paid \mydollar30.]]
   texio.write(x)
}
```

Что выводит следующий текст в `.log` файла

`I paid \mydollar 30.`

Это показывает `\mydollar` была *не* расширена во время `\directlua`предобработки. Пробел, появляющийся после `\mydollar` добавляется, когда токен команды преобразуется в его текстовое представление.

Когда вы используете `\chardef` для создания управляющей последовательности, внутренняя классификация этой управляющей последовательности (команды) в TeX приводит к тому, что она *нерасширяемых* что является совершенно иным поведением по сравнению с управляющими последовательностями, определёнными одной из команд определения макросов: \def, \edef, \gdef или \xdef. Как отмечалось выше, в процессе построения своего списка токенов `\directlua` проверяет каждый входящий токен команды на расширяемость. Если токен команды не является расширяемым, он напрямую проходит в список токенов, а его текстовое представление позже вновь появится в строке кода Lua, полученной при преобразовании токенов в списке токенов обратно в их текстовую форму.

**Краткие замечания о plain TeX и LaTeX**

Исторически исходный plain TeX Кнута определял широко используемые управляющие символы `\%`, `\&`, `\#` и `\$` с помощью `\chardef`— а не с использованием одной из стандартных команд определения макросов `\def`, `\edef`, `\gdef` или `\xdef`. Например:

```
   \chardef\#=`\#
   \chardef\$=`\$
   \chardef\%=`\%
   \chardef\&=`\&
```

Странный `` `\ `` синтаксис — это способ TeX получить числовое значение кода символа. В старом plain TeX эти управляющие символы не разворачиваются (из‑за `\chardef`) но LaTeX (или пакеты) могут переопределить их как *макросов* для расширенной функциональности — это сделает их разворачиваемыми, так что вам, возможно, нужно учитывать это.

**Как это влияет на \directlua?**

Сравним результат выполнения следующего кода в plain TeX и LaTeX. Для простоты мы будем записывать результаты в `.log` файл с помощью функции Lua API LuaTeX `texio.write()`.

```
\directlua{
   local x=[[\$150 for the "\#1" product---20\%! more than its competitor, Widget \& Co.]]
   texio.write(x)
}
```

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

```
\$150 for the "\#1" product---20\%! more than its competitor, Widget \& Co.
```

Очевидно, в plain TeX ни один из управляющих символов`\$`, `\#`, `\%` или `\&` не был развернут, потому что все они созданы с помощью `\chardef`.

При выполнении этого кода с помощью **LaTeX** документа:

```
\documentclass{article}
\begin{document}
   \directlua{local x=[[\$150 for the "\#1" product---20\%! more than its competitor, Widget \& Co.]] texio.write(x)}
\end{document}
```

выдает следующий вывод в `.log` файла

```
\protect \TU\textdollar 150 for the "\#1" product---20\%! more than its competitor, Widget \& Co.
```

Очевидно, при запуске LaTeX получается результат, отличный от plain TeX, потому что в LaTeX команда `\$` была развернута, что показывает, что это макрос.

**Примечание:** И в plain TeX, и в LaTeX `\directlua` ни один из управляющих символов не был полностью обработан `\%`, `\&`, `\#` и `\$` для генерации соответствующего символа. В процессе разворачивания, выполняемом `\directlua` токены, представляющие эти управляющие символы — или, в случае LaTeX, их разворачивание — проходят прямо в основной список токенов, который формируется.

**Примечание:** Управляющие символы образуются из одного символа, не имеющего код категории 11, например `\#`. Когда токен, представляющий управляющий символ, преобразуется обратно в текстовое представление, движки TeX не вставляют пробел после этого текста. Такое особое обращение с управляющими символами — встроенное правило работы движков TeX.

### Неразвернутые токены: подавление разворачивания

`\directlua`Предобработка — один из примеров, где движок TeX выполняет разворачивание, но вам может понадобиться *предотвратить* применение разворачивания к одному или нескольким токенам, которые иначе были бы развернуты. В качестве еще одного примера, LuaTeX (и все движки TeX) выполняют процесс разворачивания, похожий на процесс `\directlua`, когда они обрабатывают `\write` команду:

`\write file-number {⟨material⟩}`

\write предписывает движку TeX вывести `⟨material⟩`— часто содержащий команды TeX/LaTeX — в текстовый файл (`file-number`); любые разворачиваемые команды внутри `⟨material⟩` будут, если этому не помешать, развернуты до того, как `⟨material⟩` фактически будут записаны в этот файл.

Как и следовало ожидать, движки TeX предоставляют команды для подавления или управления разворачиванием:

* `\noexpand⟨token⟩`: предотвращает разворачивание одного `⟨token⟩`;
* `\unexpanded{⟨material⟩}`: предотвращает разворачивание всех разворачиваемых команд (токенов) в `⟨material⟩`. По сути, это многотокенная версия `\noexpand`;
* `\protected`: префикс, добавляемый к определениям макросов и предотвращающий разворачивание этого макроса в определенных обстоятельствах (например, во время `\directlua`, `\write` или `\edef`).

Несмотря на названия, которые подсказывают иное, и `\noexpand` и `\unexpanded` вошли *расширяемые команды* дают хорошие примеры того, как рассматривать процесс разворачивания движка TeX как выполнение «операций над токенами»: здесь операция состоит в предотвращении разворачивания одного или нескольких последующих токенов (команд). Поскольку `\noexpand` и `\unexpanded` оба являются разворачиваемыми командами, они удаляются и обрабатываются (выполняются) во время `\directlua`предобработки, когда он строит список токенов из вашего `код`.

#### \noexpand ⟨token⟩

`\noexpand ⟨token⟩` предотвращает разворачивание одного `⟨token⟩`. `\noexpand` внутри `\directlua` будет развернут (удален из ввода) и заменен результатами своего «поведения при разворачивании». Результат разворачивания `\noexpand` состоит в создании специального (скрытого) `⟨marker token⟩` который помещается перед исходным `⟨token⟩` разворачивание которого нужно подавить: этот `⟨marker token⟩` служит флагом со значением «не разворачивать следующий токен». Поскольку `\directlua` выполняет полное разворачивание, он повторно обрабатывает любые токены, возникающие в результате «поведения при разворачивании» разворачиваемой команды. Следовательно, когда разворачивание `\noexpand ⟨token⟩` завершено, LuaTeX возвращается, чтобы прочитать результат, и видит последовательность из двух токенов `⟨marker token⟩⟨token⟩` что заставляет исходный `⟨token⟩` пройти, неразвернутым, в список токенов, который строится `\directlua`.

**Пример**

Если мы напишем

```
\directlua{
   local x= "\TeX"
}
```

окружения `\TeX` макрос разворачивается в его составные токены, что в plain TeX приведет к передаче в Lua следующего текста (примечание: Lua не может обработать этот код, это лишь пример для демонстрации процесса):

`local x = "T\kern -.1667em\lower .5ex\hbox {E}\kern -.125emX"`

Если мы *подавим* разворачивание `\TeX` макроса с помощью `\noexpand`

`\directlua{local x= "\noexpand\TeX"}`

получается следующий код Lua (опять же, Lua не может выполнить этот код; это просто пример для демонстрации `\noexpand`):

`local x= "\TeX "`

Из-за `\noexpand`, `\directlua` не будет разворачиваться `\TeX` а просто позволит значению токена, представляющему `\TeX` команду, пройти без изменений в список токенов, который строится на первом этапе `\directlua`предобработки.

**Примечание:** Пробел, появляющийся после `\TeX` вводится последующим преобразованием в LuaTeX `\TeX` целочисленного значения токена обратно в его текстовое представление (внутри `tokenlist_to_cstring()` функции).

#### \unexpanded{⟨material⟩}

`\unexpanded` — это разворачиваемая команда, которая подавляет разворачивание всех токенов, образованных из `⟨material⟩`. Как мы уже отмечали, когда движок TeX выполняет разворачивание, любая разворачиваемая команда *удаляется* извлекается из ввода и *заменяется* заменяется результатами своего «поведения при разворачивании»; так что же это на самом деле означает для `\unexpanded`? Обычно во время *полное расширение*, когда процесс разворачивания для конкретной команды завершен, движок TeX продолжает читать/обрабатывать любые токены, возникающие из «поведения при разворачивании» этой команды — ему нужно дополнительно разворачивать любые созданные токены. Однако `\unexpanded` *обходит* любое дальнейшее разворачивание: вот как это делается.

Внутри движка TeX команда `\unexpanded` сначала преобразует символы и команды в `⟨material⟩` во временный список токенов, состоящий из *нерасширенные* токенов. После того как все токены созданы и сохранены в этом временном списке токенов, команда `\unexpanded` заставляет `\directlua` на *пропустить* возврат к чтению и обработке их — хотя \directlua выполняет полное разворачивание. Вместо этого эти *нерасширенные* токены проходят напрямую и включаются в основной список токенов, который строится `\directlua` (в `scan_toks()` функции). Таким образом, все в `⟨material⟩` преобразуется в токены, а процесс разворачивания для этого набора токенов пропускается. Работа `\unexpanded{⟨material⟩}` похожа на использование `\the\toks`, о котором мы поговорим ниже.

**Пример**

`\unexpanded` дает результат, похожий на `\noexpand` за исключением того, что он может предотвращать разворачивание нескольких токенов; вот пример:

```
\directlua{
   local x = "\unexpanded{\foo\bar\foobar}. But Lua can't process this code!"
}
```

что дает следующий текст как код для Lua:

`local x = "\foo \bar \foobar . But Lua can't process this code!"`

**Примечание**: После каждого имени команды есть пробелы. Это снова является следствием последующего преобразования LuaTeX неразвернутых токенов `\foo`, `\bar` и `\foobar` обратно в текст внутри `tokenlist_to_cstring()` функции.

#### определения защищенных макросов

Это `\protected` команда — это префикс, применяемый к определению макроса, чтобы предотвратить его разворачивание, когда TeX строит развернутый список токенов, например список токенов, создаваемый `\directlua`предобработки.

**Пример**

Предположим, вы определяете следующие макросы с использованием и без использования `\protected` префикса:

```
\def\macroA{"This unprotected macro contains a string"}
\protected\def\macroB{"This protected macro also contains a string"}
```

Если вы используете оператор конкатенации строк Lua (`..`) чтобы написать

```
\directlua{
   local x=\macroA..\macroB
}
```

`\directlua`предобработка выдала бы следующий код для передачи в Lua:

`local x="This unprotected macro contains a string"..\macroB`

`\macroA` не определен с помощью `\protected` поэтому он разворачивается, создавая первую часть строки для конкатенации, но `\macroB` определен с помощью `\protected` поэтому он не был развернут.

Во время предобработки функция `scan_toks()` LuaTeX создала токен для `\macroA`, распознала, что это обычная разворачиваемая команда, и развернула ее: это разворачивание создает последовательность символьных токенов, по одному символьному токену на каждый символ в `"This unprotected macro contains a string"`. Каждый символьный токен передается дальше и добавляется к формируемому списку токенов.

Когда `scan_toks()` создает токен для `\macroB` он замечает, что эта команда была определена как `\protected` и не разворачивает ее: токен, представляющий `\macroB` проходит без изменений (не развернутым) в формируемый список токенов. После того как этот список токенов построен, следующий этап предобработки в `tokenlist_to_cstring()` функции заключается в преобразовании всех токенов списка токенов обратно в их текстовое представление. Неразвернутый токен, представляющий `\macroB` обнаруживается и преобразуется в свое текстовое представление, в результате чего получается текст `\macroB` появляющийся в коде, предназначенном для Lua. Заметьте, что Lua на самом деле не может конкатенировать `"This unprotected macro contains a string"..\macroB` чтобы получить итоговую строку, потому что `\macroB` не имеет смысла в синтаксисе Lua, что приводит к ошибке `неожиданный символ рядом с '\'`.

**Любопытный факт**: Это `\protected` команда была введена $$\varepsilon\text{-}\mathrm{\TeX}$$, первым крупным расширением оригинального программного обеспечения TeX Кнута, и поддерживается всеми движками TeX, чья родословная кода включает $$\varepsilon\text{-}\mathrm{\TeX}$$.

### Неразвернутые токены: использование \the\toks в \directlua

Жизнь в программировании не была бы прежней без тех «особых случаев», с которыми приходится иметь дело, и использования `\the` в сочетании с `\toks` в `\directlua` команда — один из таких особых случаев.

#### Краткая справка о \toks

Примитив TeX `\toks` предписывает движку TeX сохранить некоторые токены для последующего использования: вместо того чтобы передаваться для дальнейшей обработки, эти токены откладываются в сторону и сохраняются в области памяти, указанной с помощью *регистр токенов*. Например, мы можем сказать движку TeX создать несколько токенов и сохранить их в регистре токенов с адресом `100` с помощью

`\toks100={Hi, \TeX! \hskip 5bp}`

Здесь TeX использует регистр токенов `100` для доступа к известному месту в своей памяти: области хранения, предназначенной для списков токенов.

Токены, представляющие все между `{` и `}` создаются, *но не разворачиваются*, и связываются в список токенов — подобный списку токенов, который мы рассматривали ранее в этой статье. Чтобы повторно использовать эти токены, мы бы написали `\the\toks100` в котором `\the` (разворачиваемая команда) предписывает TeX извлечь сохраненные токены и вставить их в место, где вы написали `\the\toks100`. Можно также думать об этом так: `\the\toks` заставляет TeX вставить некоторые токены в это место.

Это `\toks` команда *не разворачивает* ни один из токенов, которые ей поручено создать и сохранить: она просто преобразует символы и команды между `{` и `}` в токены и сохраняет их.

#### Вернемся к \directlua

В обсуждении разворачивания мы отмечали `\directlua{⟨code⟩}` выполняет *полное расширение* для `код`: удаление всех разворачиваемых команд и замена их результатом их поведения при разворачивании — продолжая *дополнительно разворачивать* любые токены, возникающие в результате начального разворачивания разворачиваемой команды.

`\the` — разворачиваемая команда, поэтому `\directlua` он развернет ее; однако, когда `\the` используется в сочетании с `\toks` внутри `\directlua`, как в `\the\toks⟨token register⟩`, вставленные токены *больше не разворачиваются*. Разворачивание `\the\toks⟨token register⟩` внедряет последовательность *нерасширенные* токенов, хранящихся в `⟨token register⟩`, непосредственно в список токенов, который строится `\directlua`: такое поведение обходит обычный процесс полного разворачивания. Фактически, эти токены проходят, *нерасширенные*, чтобы быть включенными в основной список токенов, который строится `\directlua`— этот процесс прохождения неразвернутых токенов похож по работе на `\unexpanded`, как обсуждалось ранее.

**Пример**

Предположим, мы определим макрос `\mymacro` как `\def\mymacro{\TeX}`. Он содержит всего один токен для `\TeX` команды (которая является макросом): таким образом, у нас есть разворачиваемая команда `\mymacro` которая содержит другой макрос `\TeX`, который тоже разворачиваемый.

Следующий код приведет к тому, что Lua попытается создать строковую переменную `x`:

```
\def\mymacro{\TeX}
\directlua{
   local x="\mymacro"
}
```

Внутри \\`directlua`, токен для `\mymacro` разворачивается, но это приводит к другому разворачиваемому токену, `\TeX`который далее разворачивается. В plain TeX эти разворачивания приводят к следующему тексту, передаваемому в Lua:

`local x = "T\kern -.1667em\lower .5ex\hbox {E}\kern -.125emX"`

Этот код пытается определить строку, содержащую текст, представляющий развернутую версию `\TeX` макроса. Если вы попробуете запустить этот пример, Lua попытается построить эту строку, но потерпит неудачу, сгенерировав ошибку:

`недопустимая escape-последовательность рядом с ' "T\k'.`

Позже в этой статье мы рассмотрим значение «недопустимая escape-последовательность».

Теперь сопоставим использование `\mymacro` с помещением `\TeX` токена внутрь списка токенов, созданного `\toks` команду:

```
\toks100={\TeX}
\directlua{
   local x="\the\toks100"
}
```

LuaTeX `\directlua` при обработке сгенерирует для Lua такую строку текста:

`local x = "\TeX "`

Пробельный символ после `\TeX` создается процессом преобразования командного токена в строку в LuaTeX.

**Но обратите внимание**: Это `\TeX` макрос *не* был развернут в свои составные токены. `\the\toks100` заставила токены, хранящиеся в регистре 100, быть вставленными, но и только: они *не* не разворачиваются дальше и включаются в основной список токенов, который строится `\directlua` (в `scan_toks()`функции). Помещение токенов в список токенов, созданный `\toks` — это еще один способ предотвратить разворачивание токенов.

Если мы запустим этот пример, он тоже выдаст ошибку:

`недопустимая escape-последовательность рядом с ' "\T'.`

Мы рассмотрим escape-последовательности Lua позже в статье.

## Другие команды/методы, используемые при разворачивании

В этом разделе мы рассмотрим некоторые дополнительные команды/методы TeX, которые могут быть полезны в ситуациях, когда применяется разворачивание (например, внутри `\directlua`).

### \string ⟨token⟩

`\string` — это разворачиваемая команда, которая преобразует ⟨token⟩ в последовательность символьных токенов, каждый с кодом категории 12.

Например, `\string\TeX` создаст последовательность из 4 символьных токенов `\`, `T`, `e` и `X` где каждому символу присвоен код категории 12 (включая начальный `\` символ).

Если мы напишем

```
\directlua{
   local x="I will use \string\newcommand"
   print(x)
}
```

окружения `\string` команда будет развернута, в результате чего получится последовательность символьных токенов с кодом категории 12. После `\string` будет развернута, полученные символьные токены (представляющие каждый символ в `\newcommand`) будут включены в основной список токенов, который строится `\directlua`. После того как `\directlua` завершит построение своего основного списка токенов, его составные токены преобразуются обратно в текстовое представление, что дает следующий код для передачи интерпретатору Lua:

`local x="I will use \newcommand" print(x)`

Когда этот код передается Lua, `print(x)` выведет строку `x` на экран (в консоль). Однако мы немного хитрили и специально использовали пример команды, начинающейся с `\n`. Если вы сможете запустить этот пример в локальной установке TeX, вы заметите, что Lua выводит на экран следующий текст:

```
   Я буду использовать
   ewcommand
```

Чтобы запустить этот код в Overleaf, вы можете указать LuaTeX записывать напрямую в `.log` файл с помощью функции Lua API LuaTeX `texio.write(*string*)`:

```
\directlua{
   local x="I will use \string\newcommand"
   texio.write(x)
}
```

Если вы проверите получившийся `.log` файл, вы увидите, что он также содержит

```
   Я буду использовать
   ewcommand
```

Этот неожиданный вывод связан с тем, что Lua интерпретирует `\n` в начале `**\n**ewcommand` как escape-последовательность для символа перевода строки (код символа 10): он предполагает, что вы хотите начать новую строку текста, начинающуюся с `ewcommand`. Мы обсудим escape-последовательности Lua позже в этой статье.

### \detokenize{⟨material⟩}

`\detokenize` по своим эффектам является многотокенной версией `\string` и это тоже разворачиваемая команда, которая преобразует все в `⟨material⟩` в последовательность символьных токенов с кодом категории 12—*за исключением* пробельные символы (значение ASCII/Unicode 32), которым присваивается код категории 10. `\detokenize` также вставляет завершающий пробел после имен команд, которые *управляющие слова* (например, `\foo`) но пробел не вставляется после *управляющие символы* (например, `\#`, `\%` и т. д.).

### Пример

Даже если макросы `\foohoo`, `\foo`, `\bar` и `\foobar` не определены, если вы напишете это:

```
\directlua{
   local x = "\string\foohoo\detokenize{\foo\bar\foobar}"
}
```

это создаст следующий текст в качестве кода для передачи интерпретатору Lua

`local x = "\foohoo\foo \bar \foobar "`

Если вы не используете `\string` и `\detokenize` и напишете:

`\directlua{local x = "\foohoo\foo\bar\foobar"}`

`\directlua` будет обрабатывать `\foohoo`, распознает, что это команда, и попытается развернуть ее; но поскольку `\foohoo` не определена, это приведет к ошибке:

```
   ! Неопределённая управляющая последовательность.
   l.1 \directlua{local x = "\foohoo
                      \foo\bar\foobar"}
         ?
```

Поскольку `\string` и `\detokenize` преобразуют свои аргументы в последовательность символьных токенов, `\directlua`процесс расширения действительно получает возможность обнаружить расширяемые токены команд `\foohoo`, `\foo`, `\bar`, или `\foobar`: они преобразуются в последовательности символьных токенов задолго до того, как смогут вызвать расширение.

Как отмечалось ранее, расширение команды включает удаление её из входного потока и замену результатом её «поведения при расширении». Результаты расширения (обычно токены) затем считываются движком TeX. Здесь «поведение при расширении» для `\string` и `\detokenize` заключается в поглощении символьных и командных токенов из входного потока и преобразовании их в последовательности символьных токенов, первоначально сохраняемые во временном списке токенов, который `\directlua` затем считывается. Эти символьные токены включаются в основной список токенов, формируемый `\directlua`.

Следующая схема показывает, как `\string` преобразует `\foohoo` команду в последовательность символьных токенов, создавая временный список токенов, который затем считывается `\directlua` для включения этих символьных токенов в формируемый основной список токенов.

![](/files/aae8de75a75d3e08f5f1218af421ee1bf2a91742)

Если `\string` или `\detokenize` встречают символы в своём аргументе, например, `\string a` или `\detokenize{abc}` эти символы (здесь с кодом категории 11) создают символьные токены, но с кодом категории 12.

Примечания:

Если мы вернёмся к приведённому выше примеру:

`\directlua{local x = "\string\foohoo\detokenize{\foo\bar\foobar}"}`

что даёт следующий текст в качестве кода для передачи интерпретатору Lua

`local x = "\foohoo\foo \bar \foobar "`

мы можем наблюдать следующее:

* `\detokenize` вставил пробел после каждого имени макроса, но `\string` не сделал этого.
* `\string` действует на один токен.
* В строке `"\foohoo\foo \bar \foobar "` используемой для определения `x` мы снова столкнёмся с механизмом escape-символов Lua (обсуждается ниже):

  * `\bar` начинается с `\b` которая является escape-последовательностью Lua, используемой для представления [символа backspace](https://en.wikipedia.org/wiki/Backspace) (код символа 8);
  * команды `\foohoo`, `\foo` и `\foobar` всё начинается с `\f`, escape-последовательности Lua, используемой для представления [символа formfeed](https://en.wikipedia.org/wiki/Page_break#Form_feed) (код символа 12).

  Поскольку символьные последовательности `\b` и `\f` используются внутри строки, созданной с помощью двойных кавычек `"..."` они дадут нежелательные результаты, если не принять меры для предотвращения этого с помощью так называемых *длинных скобок* метода создания строк: теперь мы можем обсудить это вместе с escape-последовательностями Lua.

## Что такое «escape-последовательности Lua»?

Языки программирования резервируют некоторые символы для «специального использования» как часть синтаксиса языка: по сути, этим символам приписывается некий особый смысл. Однако бывают случаи, когда нужно временно «отключить» особый смысл такого символа, например если вы хотите встроить его в более длинную строку, где его стандартное поведение привело бы к синтаксической ошибке. По сути, этот символ нужно обработать *без* запуска его стандартной интерпретации — пропустить его незамеченным. Для этого программисты используют технику, называемую *экранированием* в которой «специальный символ» представляется так называемой *escape-последовательностью*.

Стандартный пример (который также поддерживается Lua) — использование двойных кавычек внутри строки, где внутренние двойные кавычки экранируются с помощью escape-последовательности `\"`:

`"Когда их спросили о LuaTeX, они ответили: \"Это потрясающий движок TeX!\" Я согласился."`

Язык Lua предоставляет ряд механизмов для работы с escape-последовательностями:

* стандартные последовательности, включая `\n` (новая строка), `\r` (возврат каретки), `\\` (обратная косая черта), `\"` (двойная кавычка), `\t` (горизонтальная табуляция), `\v` (вертикальная табуляция) и `\'` (одинарная кавычка);
* `\xXX`, где `XX` — последовательность ровно из двух шестнадцатеричных цифр;
* `\ddd`, где `ddd` — последовательность из не более чем трёх десятичных цифр;
* на момент написания этой статьи (август 2019 года) последняя версия LuaTeX, хотя ещё и недоступная на Overleaf, использует Lua версии 5.3, в которой была добавлена поддержка UTF-8 escape-последовательностей: `\u{XXX}`. Этот механизм экранирования предназначен для символов Unicode, закодированных в UTF-8, где `XXX` — последовательность из одной или нескольких шестнадцатеричных цифр, представляющих кодовую точку символа. Обратите внимание, что заключающие скобки `{ }` обязательны.

### Управление escape-последовательностями

Традиционно строки определяются с помощью двойных кавычек, как в `"это строка"`; внутри такой строки можно использовать escape-последовательности: `"это строка.\nТеперь я начну с новой строки."`. Однако у Lua есть второй и *очень* удобный механизм определения строк: так называемые *длинные скобки* механизм, в котором вы определяете строку, заключая текст в `[[` и `]]`:

`[[Я — строка в длинных скобках]]`

В строке, созданной с помощью метода длинных скобок, механизм экранирования символов Lua *отключён*: escape-последовательности рассматриваются как обычные символы. Например, в строке

`[[Я — строка в длинных скобках\n строка]]`

окружения `\n` escape-последовательность не рассматривается как одиночный символ возврата каретки (ASCII-код 13), а как два обычных символа: `\` за которым следует `n`.

### Почему строки в длинных скобках так полезны?

Как мы позже увидим, LuaTeX предоставляет набор специализированных встроенных функций Lua, которые можно использовать с `\directlua` для управления поведением LuaTeX при наборе текста. Среди этих многочисленных функций есть одна под названием `tex.print(*string*)` которая позволяет передавать `*string*` текст из кода Lua обратно в LuaTeX для набора. Очень простой пример:

`\directlua{tex.print("Hello, World!")}`

что заставит LuaTeX набрать `Hello, World!`

Это `*string*` используется в `tex.print(*string*)` может также включать текст, представляющий команды TeX и LaTeX, для обработки LuaTeX. Однако команды TeX/LaTeX начинаются с `\` символа, что проблематично для строк, созданных с помощью двойных кавычек, потому что Lua попытается разобрать строку, обнаружить начальный `\` символ и интерпретировать его как начало escape-последовательности. Когда Lua пытается обработать escape-последовательность, это обычно завершается ошибкой, потому что начальный `\` в сочетании с первым символом во многих именах команд TeX/LaTeX не образует допустимую escape-последовательность, известную Lua. Например, при обработке строки вроде `"Мне нравится \LaTeX"` Lua увидит `\L` и завершится ошибкой «invalid escape sequence», и именно это является причиной ошибок, упомянутых выше.

#### Строки в длинных скобках приходят на помощь!

Метод создания (определения) строк с помощью длинных скобок чрезвычайно полезен, потому что, хотя команды TeX/LaTeX начинаются с `\` символа, метод строк в длинных скобках отключает механизм escape-последовательностей Lua. Вот короткий пример, помня, что нам нужно предотвращать расширение макросов, используя, например, `\protected` или `\noexpand`.

Предположим, мы определим `\newtest` макрос так:

`**\protected**\def\newtest#1{Аргумент: #1}`

и используем его в `\directlua` с функцией Lua API LuaTeX `tex.print()`:

```
\directlua{
   tex.print("\newtest{Hello}")
}
```

Из-за использования `\protected`, макрос `\newtest` не расширяется, что приводит к следующему тексту, передаваемому Lua:

`tex.print("\newtest {Hello}")`

Символ пробела, добавляемый после `\newtest` и перед открывающей фигурной скобкой (`{`) является побочным эффектом `\directlua`преобразования токенов команд обратно в их текстовое представление.

Этот код передаётся Lua, который затем выполняет функцию LuaTeX `tex.print()` но возникает проблема, проявляющаяся по-разному в зависимости от используемых шрифтов. В LaTeX на Overleaf вы увидите вывод примерно такой:

![](/files/69d18747960125a95bd26edcc26ccc91d3242f8c)

вместе с предупреждением в файле журнала:

```
   Missing character: There is no
   (U+000A) in font [lmroman10-regular]:+tlig;!
```

В plain TeX вы можете увидеть вывод, выглядящий примерно так:

![](/files/75bd62a134320d88ea18c8fadf3db2613a8f28ee)

В обоих случаях `\newtest` макрос не вызывается, и вывод получается не таким, как мы хотели. Ошибка вызвана механизмом escape-символов Lua: в тексте `\newtest {Hello}` имя макроса начинается с `\n` который Lua распознаёт как escape-последовательность для символа перевода строки, поэтому он заменяет `\n` на ASCII-символ 10, или в hex 0A. В сообщении об ошибке LaTeX, `U+000A` — это способ представить значение Unicode с помощью 4 шестнадцатеричных цифр.

Поскольку `\n` преобразуется в символ перевода строки, LuaTeX не видит вызов макроса, а вместо этого считает, что его просят набрать текст, начинающийся с ASCII-кода символа 10:

`⟨ASCII 10⟩ewtest {Hello}`

В зависимости от используемого шрифта LuaTeX может, а может и не может, набрать `⟨ASCII 10⟩` символ, но остальной текст выводится как есть, а `{` и `}` рассматривается как группа и не печатается.

Plain TeX даёт иной результат, потому что шрифт по умолчанию — Computer Modern Roman, у которого странная кодировка, из-за чего при обнаружении кода символа 10 набирается заглавная омега.

Чтобы избежать этих проблем, нам нужно использовать строки в длинных скобках, чтобы не применять механизм экранирования Lua. Правильный результат получается с

`\directlua{tex.print([[\newtest{Hello}]])}`

что даёт результат, показанный на следующем снимке экрана:

![](/files/3a4217cbc44110f3be00b9f307f6f39ff0a4f4ee)

### Расширение и неисполнение нерасширяемых команд

Обсуждая расширение, мы отмечали, что это процесс, в котором движок TeX *удаляет* расширяемую команду (токен) из текущего ввода и *заменяет* её результатом (или результатами), полученными от этой расширяемой команды. Поскольку \directlua выполняет *только расширение* действия (для генерации списка токенов), он *не* не продвигает обработку LuaTeX дальше этого. Как только расширяемая команда прочитана и полностью расширена, результаты этого расширения — которые часто включают нерасширяемые команды (токены) — будут включены в создаваемый список токенов, готовый к преобразованию обратно в текст для передачи в Lua.

Здесь действует важный принцип: во время *только расширение* действий, предназначенных для создания списка токенов, движки TeX, включая LuaTeX, *не выполняют* никакие нерасширяемые примитивные встроенные команды TeX.

В случае `\directlua{⟨code⟩}`, если полностью развернутая версия вашего `код` создаёт или содержит нерасширяемые команды TeX/LaTeX, они *будут переданы в Lua* (в виде текста).

#### Пример

Вот пример, демонстрирующий, что нерасширяемые примитивы не выполняются во время обработки только расширения (например, внутри `\directlua`). Предположим, мы определим макрос `\setcountreg` так:

`\def\setcountreg#1#2{\count#1=#2\relax}`

**Примечание**: Мы используем `\relax` после параметра `#2` чтобы LuaTeX не «заглядывал слишком далеко» при сканировании ввода в поисках числового значения (аргумента) для сопоставления с параметром `#2`.

Если вне `\directlua`, мы позже вызовем макрос так

```
   \setcountreg{100}{50}
   Значение в счётном регистре 100 равно \the\count100.
```

он выведет

`Значение в счётном регистре 100 равно 50.`

В этом контексте любой движок TeX обработал бы макрос `\setcountreg`— развернул бы макрос, определил аргументы и продолжил бы чтение *и выполнил бы действие* (выполнить) команды, содержащиеся в заменяющем тексте макроса (определении). Результат здесь — присвоить `50` в качестве значения, хранящегося в регистре `\count100`.

Однако когда движок TeX выполняет *только расширение* действия, как это происходит с `\directlua`, он *не будет выполнять* нерасширяемые команды, содержащиеся в определении макроса.

Если мы напишем

```
\def\setcountreg#1#2{\count#1=#2\relax}
\directlua{
   local x = [[\setcountreg{100}{50}]]
}
```

он выдаёт следующий текст в качестве кода для Lua:

`local x = [[\count 100=50\relax ]]`

Приведённый выше код Lua показывает, что внутри `\directlua` окружения `\setcountreg` он был расширен, его аргументы были определены и подставлены в соответствующий параметр (`#1` и `#2`) но дальше этого дело не идёт: нерасширяемая примитивная команда TeX `\count` была *не выполняется* во время `\directlua`обработки расширения.

Однако LuaTeX выполнит код TeX, если мы передадим получившуюся строку `x` *обратно в LuaTeX* через `tex.print(x)` вот так

```
\count100=50 % установить \count100 в начальное значение 50
\def\setcountreg#1#2{\count#1=#2\relax}
\directlua{
   local x = [[\setcountreg{100}{250}]]
   tex.print(x)
}
Значение, хранящееся в счётном регистре 100, равно \the\count100.
```

После `\directlua` завершения вывод будет

`Значение, хранящееся в счётном регистре 100, равно 250.`

что показывает, что счётный регистр `100` теперь содержит значение `250`.

Код Lua, полученный из приведённого выше примера, такой:

`local x = [[\count 100=250\relax ]] tex.print(x)`

Этот код определяет `x` как строку, созданную методом длинных скобок, который используется для предотвращения ошибок, вызванных неверными escape-последовательностями. Если бы мы использовали двойные кавычки `"..."` для определения x, сочетание символов `\c` в начале `\count` вызвало бы ошибку: `invalid escape sequence near ' "\c'`.

Вызов Lua API LuaTeX `tex.print(x)` приводит к тому, что LuaTeX выполняет последовательность кода TeX `\count 100=250\relax` и `\count100` получает значение `250` как видно из набранного вывода:

`Значение, хранящееся в счётном регистре 100, равно 250.`

#### Осторожно: макросы и Lua API LuaTeX

В приведённом выше примере мы видели, что во время `\directlua`предобработки (расширения) LuaTeX не выполнял код `\count 100=250`, который содержит `нерасширяемых` примитивную команду `\count`: чтобы запустить (выполнить) этот код, нам пришлось *передать его обратно в LuaTeX* через `tex.print()`.

`\directlua` — это лишь один случай, когда LuaTeX выполняет обработку только расширения для построения списка токенов. Есть и другие команды, выполняющие похожие действия по расширению и генерации списка токенов, например `\write` и `\edef`: эти команды также не выполняют нерасширяемые примитивы во время обработки расширения. Это общий принцип: движки TeX не выполняют нерасширяемые примитивы при построении списка токенов во время действий по обработке только расширения.

**Переписываем наш макрос с использованием Lua API LuaTeX**

Мы можем переписать `\setcountreg` макрос, используя функцию Lua API LuaTeX под названием `tex.setcount()`, тем самым избегая команд TeX для изменения значения, хранящегося в счётном регистре `100`:

```
   \def\setcount#1#2{\directlua{tex.setcount(#1,#2)}}
   \count100=50
   счётный регистр 100 содержит \the\count100\par
   \setcount{100}{250}
   счётный регистр 100 теперь содержит \the\count100\par
```

Этот код выведет:

```
счётный регистр 100 содержит 50
счётный регистр 100 теперь содержит 250
```

Здесь мы используем `tex.setcount()`, одну из многочисленных функций Lua API LuaTeX, чтобы *непосредственно обращаться к* внутренней области хранения данных LuaTeX, чтобы поместить значение `250` в ячейку памяти, представляющую счётный регистр `100`. По сути, мы *обошли* стандартные методы обработки ввода движком TeX в LuaTeX: чтение ввода, создание токенов и выполнение примитивных команд TeX. Однако здесь есть предостережение: при использовании функций Lua API LuaTeX действия по обработке только расширения *могут привести к побочным эффектам*: изменениям значений, хранящихся внутри движка TeX, которые иначе были бы невозможны с помощью чистых команд TeX/LaTeX.

**Пример: неожиданные побочные эффекты**

Вот пример, демонстрирующий *неожиданные* побочные эффекты, которые могут возникать у макросов, использующих `\directlua`. Предположим, мы напишем следующий код:

```
\def\dochange{\directlua{tex.setcount(999,12345)}}
\edef\careful{\dochange}
\the\count999
```

При выполнении этого кода набирается `12345`!

Как такое возможно? Мы не *явно* вызывали какой-либо код или макросы, чтобы поместить это значение в счётный регистр `999`. Или всё же вызывали?

Мы определили `\dochange` с `\directlua` командой, которая использует `tex.setcount()` для сохранения значения `12345` в счётном регистре `999`: в коде TeX это эквивалентно `\count999=12345`. Затем мы использовали стандартный примитив TeX `\edef` для определения макроса `\careful`— именно использование `\edef` вызывает неожиданный побочный эффект.

`\edef` полностью раскрывает свой аргумент: здесь он обнаруживает расширяемый макрос `\dochange` и расширяет его.  `\dochange` Макрос использует расширяемую команду `\directlua` которая содержит вызов Lua API; поэтому расширение `\dochange` приводит к расширению `\directlua` и это приводит к `tex.setcount()` к вызову, который изменяет значение в счётном регистре `999`.

Если мы переопределим `\dochange` так, чтобы использовать команды TeX:

```
   До: счётный регистр 999 содержит \the\count999.\par
   \def\dochange{\count999=12345\relax}
   \edef\careful{\dochange}
   После: счётный регистр 999 содержит \the\count999.\par
```

при выполнении этого кода выводится

```
До: счётный регистр 999 содержит 0.
После: счётный регистр 999 содержит 0.
```

Очевидно, это не повлияло на `\count999`. Когда `\edef` определяет `\careful` оно разворачивается `\dochange` но это разворачивание порождает только неразворачиваемые примитивы TeX: они *не выполняется* а просто *хранятся* в списке токенов, составляющем определение `\careful`.

Для полноты картины тот же принцип объясняет, почему это приводит к наборному выводу:

```
\def\dochange{\directlua{tex.print("Hello")}}
\edef\careful{\dochange}
```

## Краткое введение в Lua API LuaTeX

Как мы видели, `\directlua` не только позволяет писать обычный код Lua или смесь кода Lua и TeX/LaTeX, но и предоставляет доступ к набору дополнительных функций Lua (специфичных для LuaTeX), которые можно использовать (вызывать) для взаимодействия с внутренним устройством системы верстки LuaTeX или для прямого управления им. В этой статье мы использовали несколько функций Lua, `tex.print()`, `texio.write()`, `tex.setcount()` и эти, вместе с *множеством* других, документированы в [Справочном руководстве LuaTeX](http://www.pragma-ade.com/general/manuals/luatex.pdf) в котором группы связанных функций называются *библиотеками*.

Эти функции Lua можно рассматривать как Lua API LuaTeX (**Один**прикладной **П**программирования **И**интерфейс), которое предоставляет инструменты для создания сложных решений по верстке и инженерии документов, управляя поведением верстки LuaTeX с помощью Lua в качестве управляющего языка.

Как уже отмечалось, LuaTeX организует свой API в набор функций, которые он называет библиотеками: группы функций, связанных по назначению или действиям. Каждый набор функций предназначен для предоставления доступа к определённому аспекту внутренних процессов LuaTeX, структур данных, хранилища данных и алгоритмов верстки. Внутренне LuaTeX состоит из множества компонентов: программных библиотек/инструментов (в основном написанных на C), которые включают не только сам движок TeX, но и другие подсистемы, включая Lua, MetaPost, Kpathsea, FontForge, libpng и zlib. Эти библиотеки объединены для реализации возможностей и функций исполняемого ПО LuaTeX, и именно через Lua API пользователям предоставляется доступ к функциональности LuaTeX, возникающей благодаря интеграции и координации этих многочисленных программных компонентов.

## Некоторые примеры и подводные камни

В этом разделе мы представим ещё несколько примеров, в которых используются темы, концепции и объяснения, приведённые в этой статье.

### Использование символа тильды (\~)

Язык Lua использует `~` символ (называемый тильдой) как часть своего синтаксиса, включая синтаксис для проверки на «не равно»; например, чтобы проверить, не равна ли переменная `x` не равна `4` мы могли бы написать:

```
   local x=3
   if x ~= 4 then
   print("x is not equal to 4")
   end
```

Если мы попробуем запустить этот простой код Lua через `\directlua`:

```
\directlua{
   local x=3
   if x ~= 4 then
   print("x is not equal to 4")
   end
}
```

мы получим ошибку:

`[\directlua]:1: 'then' expected near '\'.`

Это странно, потому что наш код корректен: мы использовали `'then'` и в нём нет `\` символа в нашем коде, так что же пошло не так? Чтобы понять это, нужно помнить, что для TeX/LaTeX `~` обычно определяется как «специальный символ» с кодом категории 13: так называемые активные символы, которые являются мини-макросами и потому подлежат разворачиванию. Когда `\directlua` обнаруживает `~` символ, он разворачивается путем *удаления его* извлекается из ввода и *заменяя её* и замены его результатом разворачивания. При использовании plain TeX получающийся текст (код), который LuaTeX создаёт и передаёт интерпретатору Lua, на самом деле не содержит `~` символа и выглядит так:

`local x=3 if x \penalty \@M \ = 4 then print("x is not equal to 4") end`

Это `~` символ был *удаляется* и *развёрнут* в составляющие команды — приведённый выше код Lua является результатом определения активного символа в plain TeX `~`. Теперь мы видим, почему Lua отвечает ошибкой `'then' expected near '\'`— он начинает разбирать этот код, но встречает слово `\penalty` которое ничего не значит для Lua и вызывает синтаксическую ошибку.

Чтобы исправить это,  `~` символ должен иметь безопасный код категории в момент, когда `\directlua` обрабатывает ваш код; например, мы можем временно изменить код категории `~` на 11 (буква), заключив код в группу:

```
\begingroup
\catcode`\~=11
\directlua{
   local x=3
   if x ~= 4 then
   print("x is not equal to 4")
   end
}
\endgroup
```

Этот код работает как ожидается, и `x is not equal to 4` выводится в консоль. Есть и другие варианты: мы можем использовать разворачиваемые команды `\noexpand` или `\string`.

#### Использование \string⟨token⟩

Мы можем применить `\string` к однобуквенному `⟨token⟩` `~` который имеет код категории 13 (активный символ); `\string` преобразует `~` символу, чтобы сгенерировать токен символа с кодом категории 12. Если мы сделаем

```
\directlua{
   local x=3
   if x \string~= 4 then
   print("x is not equal to 4")
   end
}
```

это даст нам нужный код Lua:

`local x=3 if x ~= 4 then tex.print("x is not equal to 4") end`

#### Использование \noexpand⟨token⟩

Мы можем использовать `\noexpand~` для подавления разворачивания активного символа `~`

```
\directlua{
   local x=3
   if x \noexpand~= 4 then
   print("x is not equal to 4")
   end
}
```

Неразвернутый `~` токен проходит в список токенов, формируемый в `\directlua` и будет преобразован обратно в текст, что даст рабочий код Lua.

### Использование символа \#

В языке Lua `#` символ можно использовать для определения длины таблицы. Однако если мы попробуем следующий код

```
\directlua{
   local tbl = {}
   tbl[1] = "Hello"
   tbl[2] = "World"
   tex.print("Table length is "..#tbl)
}
```

мы могли бы ожидать, что LuaTeX выведет набором

`Длина таблицы — 2`

но он выдаёт ошибку:

`\directlua]:1: попытка получить длину числового значения`

Эта ошибка возникает потому, что `#` символ обычно имеет код категории 6 (параметр макроса) — `#` символ используется в TeX/LaTeX двумя способами: для обозначения параметров макросов (`#1`, `#2`… `#9`) и текста замены в шаблонах выравнивания (для `\halign` и `\valign`).

Когда `\directlua` когда генерирует токены для построения своего списка токенов, он видит `#` символ с кодом категории 6 и создаёт подходящий токен символа для его представления. Когда приходит время преобразовать конечный список токенов обратно в текстовую форму, токен символа # (с кодом категории 6) обрабатывается особым образом: он выводится как *два последовательных символа*: `##`, в результате чего Lua передаётся следующий код:

`local tbl = {} tbl[1] = "Hello" tbl[2] = "World" print(##tbl)`

При преобразовании в код Lua исходный `#` был удвоен, и это вызывает ошибку:

`\directlua]:1: попытка получить длину числового значения`

Эта проблема возникает из-за синтаксиса TeX, который использует двойной символ решётки `##` для представления или генерации одного `#` токена; этот синтаксис используется в макросах, которые определяют другие макросы, принимающие параметры, или в макросах, используемых для создания шаблонов для `\halign` или `\valign` команд построения таблиц. Это довольно запутанно, так что давайте посмотрим на пример.

#### Пример

Предположим, мы определим макрос `\mymacro` который принимает один параметр, `#1`, но он также определяет второй макрос `\foo` который сам принимает один параметр. Чтобы различить параметр `#1` используемый с `\mymacro` и необходимость определить `\foo` так, чтобы он использовал свой собственный параметр `#1` синтаксис TeX требует, чтобы вы использовали `##1` внутри `\mymacro` для представления параметра, используемого с `\foo`:

`\def\mymacro#1{\def\foo##1{#1 Hello##1}}`

Если бы вы написали `\mymacro{Hey!}` это определило бы макрос `\foo` как

`\def\foo#1{Hey! Hello#1}`

Обратите внимание, что `\mymacro`параметр `#1` (`Hey!`») был включён в определение `\foo` и последовательность `##1` была преобразована в `#1` в определении `\foo`. Итак, мы можем использовать `\foo` так:

`\foo{, World!}`

для набора `Эй! Привет, мир!`

Мы можем решить `\directlua`обработку `#` символа, временно изменив его код категории до того, как LuaTeX обработает код. Например:

```
\begingroup
   \catcode`\#=11
   \directlua{
   local tbl = {}
   tbl[1] = "Hello"
   tbl[2] = "World"
   tex.print("Table length is "..#tbl)
}
\endgroup
```

Это генерирует код Lua

```
local tbl = {} tbl[1] = "Hello" tbl[2] = "World" tex.print("Table length is "..#tbl)
```

что выводит ожидаемый нами результат:

`Длина таблицы — 2`

### Использование символа %

В TeX/LaTeX символ `%` обычно используется для включения однострочных комментариев в код: чтобы сообщить движку TeX, что он должен игнорировать всё с этого места до конца строки, в которой `%` он написан. Однако в языке Lua символ `%` используется в некоторых очень полезных функциях обработки строк, таких как `string.format(...)`, `string.gmatch(...)`, и `string.gsub(...)` в которых символ `%` играет важную роль как часть синтаксиса этих функций.

При использовании в TeX/LaTeX символ `%` работает как символ комментария, потому что ему присвоен код категории 14. Чтобы он вел себя как обычный символ и чтобы отключить его обычное поведение TeX/LaTeX, нужно изменить его код категории на безопасное значение, например 12.  `\directlua` Пример ниже использует ряд приёмов, обсуждавшихся ранее в статье, а также один, о котором мы ещё не упоминали: ``\catcode`\^^M=12``, что позволяет нам использовать комментарии Lua в нашем коде; об этом говорится ниже.

#### Пример

Следующие примеры заимствованы из [lua-users.org](http://lua-users.org/wiki/StringLibraryTutorial), с соответствующими изменениями для использования в `\directlua`.

```
\documentclass{article}
\begin{document}
\begingroup
\ttfamily
\let\\\relax
\catcode`\^^M=12 %<---we further explore this below!
\catcode`\%=12
\directlua{
   local str -- declare a local variable to hold the result

   tex.print("Using string.format():".."\\par")

   str=string.format("%s %q", "Hello", "Lua user!") -- string and quoted string
   tex.print(str.."\\par")
   str = string.format("%c%c%c", 76, 117, 97) -- char
   tex.print(str.."\\par")
   str=string.format("%e, %E", math.pi, math.pi) -- exponent
   tex.print(str.."\\par")
   str=string.format("%f", math.pi) -- float
   tex.print(str.."\\par")
   str=string.format("%g, %g", math.pi, 10^9) -- float or exponent
   tex.print(str.."\\par")
   str = string.format("%o, %x, %X", 99, 125, 125)  -- octal, hexadecimal, hexadecimal
   tex.print(str.."\\par")

   tex.print("\\vskip3mm".."Using string.gmatch():".."\\par")

   for word in string.gmatch("Hello TeX user", "%a+") do
      tex.print(word.."\\par")
   end

   tex.print("\\vskip3mm".."Using string.gsub():".."\\par")
   str=string.gsub("banana", "(an)", "%1-") -- capture any occurrences of "an" and replace
   tex.print(str.."\\par")
}
\endgroup
\end{document}
```

Следующий снимок экрана показывает результат набора приведённого выше кода:

![Использование строковых функций Lua в \directlua](/files/1df3b6615f99a7216e43c74b86d5fc6802341e39)

## Почему код Lua показан в одну строку?

Как вы могли заметить, все (сгенерированные) фрагменты кода Lua, показанные в примерах этой статьи, представлены одной строкой текста: переносы строк, изначально присутствовавшие в `\directlua` фрагментах кода, не сохраняются. Почему? Потому что переносы строк в коде Lua были *удалены* на этапе предварительной обработки LuaTeX в `\directlua`, из-за чего код Lua становится одной длинной строкой текста. Такое поведение связано с тем, как движки TeX обрабатывают символы конца строки, обозначаемые `\r` (возврат каретки) и `\n` (перевод строки) в программной литературе. Зачем нам нужно вникать в эти детали, станет ясно, когда мы обсудим использование механизмов Lua для комментирования фрагментов кода.

Когда программа записывает (сохраняет) текстовый файл, каждая отдельная строка текста завершается так называемыми символами «новой строки» — фактический символ(ы) новой строки зависят от приложения и операционной системы, используемых для записи этого файла. В Википедии есть [интересная статья](https://en.wikipedia.org/wiki/Newline) в которой рассматривается история/эволюция символов новой строки, используемых сегодня.

В любом текстовом файле отдельные строки могут завершаться различными комбинациями символов, называемыми возвратом каретки (символ ASCII/Unicode 13) и/или переводом строки (символ ASCII/Unicode 10), которые обозначаются `\r` и `\n` соответственно. Поскольку движки TeX спроектированы как независимые от платформы, им нужен способ обойти изначально зависящую от платформы природу окончаний строк, используемых в текстовых файлах. Естественно, у движков TeX есть встроенный (но настраиваемый) способ работы с символами конца строки.

### Как движки TeX обрабатывают окончания строк

Когда LuaTeX обрабатывает `\directlua{⟨code⟩}` он считывает текст, содержащийся в вашем `код` и применяет стандартные методы движка TeX для обработки любых окончаний строк, содержащихся в вашем `код`. По умолчанию эти стандартные методы TeX приводят к тому, что все символы конца строки (возврат каретки и перевод строки) удаляются и заменяются пробелами. Мы говорим «по умолчанию», потому что обработка символов конца строки движком TeX может быть изменена с помощью настраиваемого пользователем параметра под названием `\endlinechar`. Здесь мы дадим краткий обзор в два шага, но более подробные сведения можно найти в статье Overleaf [Введение в \endlinechar: как TeX читает строки из текстовых файлов](/latex/ru/podrobnye-stati/05-an-introduction-to-endlinechar-how-tex-reads-lines-from-text-files.md).

#### Шаг 1: TeX вставляет собственный символ конца строки

После чтения строки текста из входного файла движки TeX немедленно удаляют любые `\r` или `\n` символы в конце этой строки. Затем движки TeX *вставляют* (добавляют обратно) собственный символ конца строки в конец этой строки. Этот символ определяется значением настраиваемого пользователем параметра TeX под названием `\endlinechar` и именно благодаря этому механизму движки TeX могут обрабатывать символы конца строки независимо от платформы: они выбирают и задают символ конца строки независимо от того, что изначально содержалось во входном текстовом файле.

Обычно движки TeX используют настройку

`\endlinechar=13`

которая соответствует символу возврата каретки (`\r`). Однако пользователь всегда может присвоить в качестве значения `\endlinechar`другой код символа — как мы увидим позже в этой статье.

Следовательно, любые символы конца строки, содержащиеся в вашем `код` которые должны быть обработаны `\directlua{⟨code⟩}` удаляются и заменяются одним символом, определяемым самим движком TeX. Обратите внимание, что движки TeX выполняют эту обработку конца строки сразу после чтения новой строки текста из файла и *до* обработки любых символов в этой строке (для генерации токенов). Однако на этом история не заканчивается: то, что движок TeX *делает* с этими символами конца строки (которые он вставил), объясняет, почему код Lua становится одной-единственной строкой.

#### Шаг 2: TeX преобразует свой символ конца строки в пробел

Помимо вставки собственного символа конца строки, определяемого значением `\endlinechar`, движки TeX также используют код категории 5 для символов, которые должны быть *обработаны как* символ конца строки. В результате движки TeX обычно работают с:

1. символом конца строки, определённым `\endlinechar`;
2. этот же символ *обычно* которому присвоен код категории 5.

Именно то, что TeX делает с этим символом конца строки, объясняет нашу загадку с однострочным кодом Lua. Когда движок TeX обрабатывает строку ввода, он в конце концов обнаруживает последний символ в этой строке: символ, определённый `\endlinechar`. Обычно этому символу присвоен код категории 5, что заставляет TeX *заменять его* пробельным символом: то есть в конце строк TeX, по сути, удаляет свой символ конца строки и заменяет его пробелом. Кстати, движки TeX также используют символы с кодом категории 5 для обнаружения пустых строк и начала нового абзаца, но мы не будем рассматривать это здесь.

Разумеется, поскольку это TeX, вы можете проделывать всевозможные специальные приёмы макропрограммирования, переназначая `\endlinechar` на другой символ и/или присваивая символу, назначенному `\endlinechar` код категории по вашему выбору.

Если вы хотите предотвратить превращение кода Lua в одну-единственную строку текста, вы можете либо (временно) изменить значение, присвоенное `\endlinechar` либо изменить код категории стандартного символа конца строки `\r`.

### Странная нотация ^^ в TeX

В следующих разделах мы столкнёмся с необычной `^^` нотацией, известной как «механизм расширенных символов». Она была разработана Кнутом как способ облегчить ввод «управляющих символов», таких как символы конца строки, табуляции и так далее. Например:

* `^^J` обозначает код символа 10 (`\n`, перевод строки);
* `^^M` обозначает код символа 13 (`\r`, возврат каретки).

Последовательности символов, такие как `^^M` преобразуются в соответствующие коды символов на раннем этапе процесса сканирования входных данных TeX, когда TeX считывает входные символы для генерации соответствующих токенов символов.

### Изменение символа, назначенного \endlinechar

Помня о том, что нам всё ещё нужно предотвратить разворачивание `~` символа, мы можем написать

```
\begingroup
\endlinechar=10 % Change the end-of-line character to \n
\directlua{
   local x=3
   if x \noexpand~= 4 then
   print("x is not equal to 4")
   end
}% don’t want the \n appearing here
\endgroup% or a \n here
```

Приведённая выше настройка для `\endlinechar` заставляет LuaTeX добавлять код символа 10 (`\n`, перевод строки) в конец каждой строки, которую он читает. Мы делаем это потому, что `\n` (перевод строки) обычно имеет код категории 12, что можно проверить, написав ``\the\catcode`\^^J``. Поскольку `\n` не имеет кода категории 5, LuaTeX не преобразует его в пробел, поэтому он остаётся в конце каждой строки, считываемой LuaTeX. В результате символ с кодом 10 остаётся в конце каждой строки, а значит, попадает в список токенов, формируемый `\directlua` и затем снова появляется в коде Lua, когда список токенов преобразуется в текст. После указанного изменения код Lua передаётся интерпретатору Lua в виде следующей последовательности символов:

**\n**local x=3\*\*\n**if x \~= 4 then**\n**print("x is not equal to 4")**\n**end**\n\*\*

где **\n** нотация обозначает код символа 10 *не* какой-то неизвестный макрос `\n`. Теперь интерпретатор Lua увидит переносы строк в коде точно так же, как он был изначально написан в `\directlua` команду:

```
   local x=3
   if x ~= 4 then
   print("x is not equal to 4")
   end
```

Кстати, обратите внимание, что самый первый символ в строке кода Lua — это `\n` (перед `local` ключевым словом). Это `\n` обусловлено строкой

`\directlua{`

потому что сразу после открывающей `{` и это тоже сохраняется. Чтобы предотвратить это, можно написать

`\directlua{%`

### Изменение кода категории \r

Чтобы сохранить переносы строк в нашем коде Lua, мы также можем изменить код категории `\r` на что-то, отличное от 5, чтобы `\r` он больше не распознавался (не рассматривался) как символ конца строки. При этом приём LuaTeX по-прежнему использует `\endlinechar=13` и по-прежнему будет добавлять `\r` в конец каждой строки; однако, поскольку `\r` больше не имеет кода категории 5, LuaTeX не распознает `\r` этот символ как символ конца строки: он не будет преобразован в пробел и пройдёт через обработку без изменений, появившись в коде Lua.

Помня о том, что нам всё ещё нужно предотвратить разворачивание `~` символа, мы можем написать

```
\begingroup
\catcode`\^^M=12 % change category code of \r to 12
\directlua{
   local x=3
   if x \noexpand~= 4 then
   print("x is not equal to 4")
   end
}
\endgroup
```

В этом случае код Lua передаётся интерпретатору Lua как:

**\r**local x=3\*\*\r**if x \~= 4 then**\r**print("x is not equal to 4")**\r**end**\r\*\*

где `\r` нотация обозначает код символа 13, а не какой-то неизвестный макрос `\r`. Как и в `\endlinechar` примере, интерпретатор Lua теперь увидит переносы строк в коде точно так же, как он был изначально написан в `\directlua` команду:

```
   local x=3
   if x ~= 4 then
   print("x is not equal to 4")
   end
```

Кстати, ещё раз отметим, что самый первый символ в строке кода Lua — это `\r` (перед ключевым словом local): это тоже возникает из строки

`\directlua{`

#### Почему \r использует код категории 12, а не код категории 11?

Ответ связан с риском случайного внесения ошибок, вызванных `\r` (с кодом категории 11), добавляемого в конец команд TeX/LaTeX, считываемых из нашего входного файла. Возьмём такой пример:

```
\begingroup
\catcode`\^^M=11 % изменить код категории \r на 11
\directlua{
   local x=3
   if x \noexpand~= 4 then
   print("x is not equal to 4")
   end
}
\endgroup
```

что порождает ошибку:

```
   ! Неопределённая управляющая последовательность.
   l.9 \endgroup
```

Как это может быть правдой, ведь `\endgroup` — это стандартная примитивная команда TeX? Причина ошибки довольно тонкая: когда LuaTeX считывал последнюю строку текста — ту, что содержала `\endgroup`— он также добавил `\endlinechar` символ `\r` в конец этой строки. Теперь внутри своей памяти LuaTeX видит последовательность символов

`\endgroup\r`

где мы используем `\r` для обозначения символа с кодом 13 — а не имени какой-то неизвестной макрокоманды TeX `\r`.

В тот момент, когда LuaTeX считывал эту строку из нашего текстового файла, исходный `\begingroup` по-прежнему действует: мы находимся внутри группы, которая ещё не была закрыта выполнением соответствующей `\endgroup` команды — что привело бы к тому, что `\r` снова принял бы прежнее значение кода категории 5.

Когда LuaTeX начинает обрабатывать (создавать токены из) строку текста `\endgroup\r` он распознаёт первый символ `\` как символ экранирования, который запускает LuaTeX в поиск имени команды. Чтобы определить имя команды, LuaTeX ищет последовательность символов с кодом категории 11, но поскольку `\r` тоже имеет код категории 11, LuaTeX думает, что `\r` символ (всё ещё с кодом категории 11) образует *часть команды* с именем `\endgroup\r` которой, разумеется, не существует, поэтому LuaTeX сообщает об `Неопределённая управляющая последовательность` ошибке. Поэтому мы использовали код категории 12, а не 11.

Поскольку сообщение об ошибке LuaTeX было выведено в консоль, мы не смогли легко увидеть/заметить `\r` символ, поэтому было неочевидно, что вызвало ошибку.

### Почему мы вообще беспокоимся о концах строк?

Причина в том, чтобы позволить использовать в вашем коде способ комментирования Lua! Вы можете использовать стандартный механизм LuaTeX для добавления `%` символов, чтобы комментировать отдельные строки в вашем коде; однако язык Lua имеет свой собственный, очень полезный, *многострочный* механизм комментариев, которым вы, возможно, захотите воспользоваться.

Начнём с того, что посмотрим, что произойдёт, если мы попробуем использовать однострочные комментарии языка Lua, не решая проблему переносов строк. В то время как TeX использует символ `%` для комментирования отдельных строк кода, в Lua используется двойной дефис: `--`.

Что произойдёт, если мы попробуем запустить это:

```
\directlua{
   local x=3
   if x \noexpand~= 4 then
   -- Я собираюсь вывести результат этого сложного теста
   print("x is not equal to 4")
   end
}
```

Получаем ошибку:

`[\directlua]:1: ожидалось 'end' рядом с <eof>`

Эта ошибка вызвана отсутствием переносов строк в коде Lua, передаваемом интерпретатору, который видит только одну непрерывную строку, в которой комментарий начинается в середине этой строки:

```

local x=3 if x ~= 4 then -- Я собираюсь вывести результат этого сложного теста print("x не равно 4") end
```

Всё после `**local x=3 if x ~= 4 then**` считается закомментированным, из-за чего интерпретатор видит неполный фрагмент кода Lua, что приводит к ошибке

`'end' ожидалось рядом с <eof>`.

где `<eof>` означает конец файла.

Как вы, вероятно, догадались, мы должны исправить это, обеспечив передачу переносов строк в результирующий код Lua, что можно сделать, например, изменив код категории `\r` на 12:

```
\begingroup
\catcode`\^^M=12 % change category code of \r to 12
\directlua{
   local x=3
   if x \noexpand~= 4 then
   -- Я собираюсь вывести результат этого сложного теста
   print("x is not equal to 4")
   end
}
\endgroup
```

Теперь интерпретатор Lua видит строку, но она содержит `\r` переносы строк, как написано в `\directlua` фрагменте:

**\r**local x=3\*\*\r**if x \~= 4 then**\r\*\*-- Я собираюсь вывести результат этого сложного теста\*\*\r**tex.print("x не равно 4")**\r**end**\r\*\*

Фактически это эквивалентно записи

```
   local x=3
   if x \noexpand~= 4 then
   -- Я собираюсь вывести результат этого сложного теста
   print("x is not equal to 4")
   end
```

что означает, что Lua может корректно обработать этот код и проигнорировать закомментированную строку.

**Блочные комментарии**

Язык Lua также поддерживает синтаксис, который он называет [«блочным комментарием»](https://www.lua.org/pil/1.3.html) (или *длинный комментарий*): они начинаются с `--[[` и действуют до соответствующего `]]`. Мы можем использовать этот удобный синтаксис, чтобы писать многострочные комментарии или комментировать фрагменты кода, которые хотим временно убрать:

```
\begingroup
\catcode`\^^M=12 % change category code of \r to 12
\directlua{
   local x=3
   if x \noexpand~= 4 then
   --[[ Я собираюсь вывести результат этого сложного теста
   просто потому, что это действительно
   такой удивительный вывод]]
   print("x is not equal to 4")
   end
}
\endgroup
```

## В заключение

Прежде всего, поздравляем, если вы сумели прочитать эту обширную статью! Мы постарались подготовить достаточно подробное руководство по понятиям и темам, связанным с TeX, которое даёт необходимую базу, чтобы извлечь максимум из LuaTeX с помощью `\directlua` команды. Мы надеемся, что у нас получилась статья, которая носит познавательный характер и приносит что-то полезное и ценное сообществу пользователей Overleaf и не только. Как всегда, мы будем рады получить отзывы, так что, пожалуйста, [свяжитесь с нами](https://www.overleaf.com/contact) делитесь

Счастливого $$\text{Lua}\mathrm{\TeX}\text{-ing!}$$ от Грэма Дугласа и команды Overleaf.

### И наконец... просто используйте пакет luacode

Хотя TeX и Lua работают принципиально по-разному, у этих языков есть ряд символов, которые имеют «особые значения» в контексте каждого языка — такие как \\, %, \~, #, ^, & — конечно, Lua и TeX придают этим особым значениям *очень* разные назначения. Наше исследование проблемных символов показывает, почему могут возникать трудности и как их можно устранить; однако вручную исправлять множество небольших фрагментов кода Lua может быть довольно утомительно, поэтому большинство пользователей предпочитают использовать пакеты LaTeX, которые снимают эти сложности. Один из таких пакетов — [`luacode`](https://ctan.org/pkg/luacode?lang=en) который предоставляет набор функций, призванных упростить работу с `\directlua`, но, по крайней мере, теперь вы, возможно, лучше понимаете проблемы, `luacode` которые он решает за вас.


---

# 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://overleaf-pro.ayaka.space/latex/ru/podrobnye-stati/09-an-introduction-to-luatex-part-2-understanding-directlua.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.
