![Как написать макрос, который принимает аргументы, содержащие абзацы?](https://rvso.com/image/391694/%D0%9A%D0%B0%D0%BA%20%D0%BD%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%80%D0%BE%D1%81%2C%20%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D1%8B%D0%B9%20%D0%BF%D1%80%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%B5%D1%82%20%D0%B0%D1%80%D0%B3%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%2C%20%D1%81%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D1%89%D0%B8%D0%B5%20%D0%B0%D0%B1%D0%B7%D0%B0%D1%86%D1%8B%3F.png)
Я пытаюсь написать макрос, который принимает аргументы, содержащие абзацы. Если вы напишете обычный макрос, и один из его аргументов будет содержать абзац, он сломается:
\documentclass{article}
\usepackage[utf8]{inputenc}
\title{test}
\begin{document}
\maketitle
\section{Introduction}
\def\mymacro#1{#1}
\mymacro{This
contains a paragraph}
\end{document}
Это приводит к ошибке:
Runaway argument? {This ! Paragraph ended before \mymacro was complete. <to be read again> \par l.15
Поэтому я попытался переопределить \par
макрос для расширения аргументов, но теперь он не прекращает компиляцию:
\documentclass{article}
\usepackage[utf8]{inputenc}
\title{test}
\begin{document}
\maketitle
\section{Introduction}
\gdef\oldpar{\par}
\def\mymacro{\gdef\par{}\mymacroi}
\def\mymacroi#1{#1\gdef\par{\oldpar}}
\mymacro{foo
bar}
\end{document}
решение1
Во времена написания TeX обработка одной страницы документа занимала несколько минут, а подсветка синтаксиса еще не существовала, поэтому было бы неплохо иметь какой-то механизм для обнаружения того, что вы забыли a }
. \def
По умолчанию a не допускает токен, \par
если вы явно не укажете, что это a \long\def
:
\def\mymacro#1{#1}
LaTeX, с другой стороны, использует это по умолчанию, поэтому, если вы используете правильные команды LaTeX ( \def
не следует использовать в документах LaTeX), \newcommand
создает \long\def
по умолчанию. Если вам нужно «короткое», \def
то вы используете \newcommand*
.
xparse
возвращает короткий аргумент по умолчанию, но позволяет определить \long
макрос с помощью +
модификатора аргумента:
\NewDocumentCommand\mymacro{ m}{#1}% \def
\NewDocumentCommand\mymacro{+m}{#1}% \long\def
Ваша вторая попытка умна и могла бы сработать, если бы не два обстоятельства.
Первое, что вы используете \gdef\oldpar{\par}
, а затем \gdef\par{\oldpar}
. Как только вырасширять \par
вы получаете \oldpar
который при расширении дает \par
который при расширении дает который при расширении дает \oldpar
который при расширении дает \par
который при расширении дает \oldpar
... Работает вечно :/
В этом случае вам необходимо использовать \let
(или \global\let
иметь глобальный эффект): \let\oldpar\par
. Это создает точную копию \par
named \oldpar
, которая не зависит от того, что есть \par
.
Во-вторых, проверка аргументов на выход из-под контроля реализована на более низком уровне, независимо от определения \par
, поэтому это приведет к той же ошибке:
\let\par\relax
\def\mymacro#1{#1}
\mymacro{foo
bar}
потому что когда TeX видит два \endlinechar
токена (что по умолчанию является пробелом), TeX вставляет неявный \par
токен, что вызывает Runaway argument
ошибку. Зная это, тогда:
\newcount\oldELchar
\oldELchar=\endlinechar
\def\mymacro{\endlinechar=-1\relax\mymacroi}
\def\mymacroi#1{#1\endlinechar=\oldELchar}
\mymacro{foo
bar}
Ошибка не возникнет, но новая строка больше не будет пробелом.
решение2
Аргумент макроса, определенного с помощью, \def
не допускает \par
токены. Он также не допускает пустые строки, поскольку они преобразуются в \par
во время фазы, в которой TeX обрабатывает текстовый ввод в токены. Обратите внимание, что переопределение \par
бесполезно в этом отношении, поскольку этоименно тактокен \par
, который запрещен, независимо от его значения.
Решение: создайте свой макрос \long
.
\long\def\mymacro#1{#1}
Лучшее решение:
\newcommand{\mymacro}[1]{#1}
потому что \newcommand
использует \long\def
внутренне. Вариант \newcommand*
вместо этого использует \def
без префикса.