如何正确使用分隔宏

如何正确使用分隔宏

我读到过关于分隔宏的内容这个答案。实际上,我想做类似以下的事情:

\documentclass[a4paper]{article}

\def\insert this[#1]{%
  this macro: #1\\
}

\def\insert that[#1]{%
  that macro: #1\\
}

\begin{document}
  \insert this[test]
  \insert that[other]
\end{document}

我希望上面的 MWE 首先求值为 ,this macro: test而在第二行,它应该求值为that macro: other。但是,当然,第二个定义会覆盖第一个定义,因此它不起作用。但是,是否可以使用如上所示的构造?我的主要想法是,我想避免输入不同的繁琐宏,而是始终使用 eg\insert来执行特殊任务。(我想避免使用\insertthismacro和,\insertthatmacro因为它更难阅读,也更难记住。)

奖励:我还想将其扩展到环境,如下面的伪代码所示:

\begin{fancyenvironment} 这个[测试] 那个[测试] \end{fancyenvironment}

这样我甚至不需要调用宏,但\insert每行都假定如此。我用 做了一些实验\@tfor,我也在链接的答案中看到了这一点,但\@tfor循环遍历单个字符,而不是遍历行。

答案1

如果你

\def\foo this[#1]{...}

你是要求紧接着;然后\foothis[参数#1,一直到找到](相对)括号级别零为止。

如果你以后这样做

\def\foo that[#1]{...}

您正在重新定义\foo并要求它后面跟着that。然后调用

\foo this[xyz]

会引发错误,因为\foo根据当前的定义。

你可以做

\def\foo #1[#2]{#1 macro: #2}

也许用于#1检查是否是this或者that做进一步的处理。

不过要小心\def。在使用它之前,最好添加

\newcommand{\foo}{}

这样 LaTeX 就会告诉你是否\foo已经有定义。可能然后决定重新定义命令,但前提是你知道恰恰你在做什么。

您正在用代码重新定义\insert,这是一个非常糟糕的想法,因为figuretable\marginpar\footnote停止工作并引发非常奇怪的错误。

答案2

TeX 宏实际上不能像这样“重载”。

\def\myinsert this[#1]{%
  this macro: #1\\
}

\def\myinsert that[#1]{%
  that macro: #1\\
}

在这两种情况下,您都会定义一个名为 的宏\myinsert。这两个定义之间的唯一区别在于它们如何获取参数。由于只能有一个名称为 的宏,因此第二个定义将覆盖第一个定义,并且从现在开始\myinsertLaTeX 将期望\myinsert其后跟。另请参阅that[...]埃格尔的解释他的回答

最初我想写成在 TeX 中根本无法重载宏,但如果你仔细想想,就会发现 LaTeX 对可选参数所做的操作是一种重载。因此重载是可能的,你只需在 TeX 中自己完成准备工作(并且可以\newcommand在 LaTeX 中使用可选参数的有限重载子集)。


您只需将“this”或“that”作为第一个参数,然后定义宏以执行相应的操作即可。一个简单的解决方案是使用辅助宏实现的。其中调用\myinsert <action>[<arg>]名为的辅助宏\myinsert@<action>。可以通过测试被调用的宏是否实际定义来扩展这个想法。

\documentclass[a4paper]{article}

\makeatletter
\newcommand{\myinsert@this}[1]{%
  this macro: #1\\
}
\newcommand{\myinsert@that}[1]{%
  that macro: #1\\
}
\newcommand{\myinsert@ooh}[1]{%
  ooh: #1\\
}
\makeatother

\newcommand*{\myinsert}{}% <- check that the name is not taken already
\def\myinsert #1[#2]{%
  \csname myinsert@#1\endcsname{#2}%
}

\begin{document}
  \myinsert this[test]
  \myinsert that[other]
  \myinsert ooh[other]
\end{document}

但我并不认为这是个好主意。我确实觉得这很脆弱。LaTeX 宏通常出于某种原因使用不同的语法。


如果你不喜欢

\myinsertthismacro{...}

你为什么不尝试

\myinsert{this}{...}

或键值接口

\myinsert[action=that]{...}

我认为这两个建议与预期并不相差太远

\myinsert this[...]

在可读性方面,它们的优势在于它们是通常的 LaTeX 语法,因此更为人熟悉。


我在这个答案中使用了,\myinsert因为\insert\insert是一个原始的不应该重新定义(至少如果你不想破坏\footnotes其他东西的话)。

答案3

在讨论中TeX 如何查找分隔参数?我试图详细说明 (La)TeX 如何处理分隔和非分隔参数。


使用分隔参数,您可以轻松地让您的\insert宏(最好这样称呼它,例如)\INSERT作为第一个参数捕获一个由左括号分隔的参数,然后捕获由右括号分隔的第二个参数,然后将第一个参数提供给另一个也使用分隔参数的宏,根据第一个参数是否
为空、是否
为短语“this”、
是否为短语“that”、
是否为其他内容进行分叉。

定义环境fancyenvironment可以通过以下方式完成:逐字包裹。

verbatim 包确实提供了定义环境的方法,这些环境逐行处理输入并搜索结束相关环境的序列。

您可以在标记寄存器中使用其框架逐行累积在 verbatim-catcode-régime 下标记的输入,并\INSERT在每行开头添加 -command。

在环境结束时,您可以将令牌寄存器的内容“刷新”到\scantokens正常的 catcode 机制下。
如果您没有可用的 eTeX 扩展,您可以将未扩展的令牌寄存器的内容写入临时文件,然后\input让 LaTeX 在正常的 catcode 机制下处理该临时文件。

\documentclass[a4paper]{article}
\usepackage{verbatim}
\makeatletter
%%----------------------------------------------------------------------
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
%%----------------------------------------------------------------------
%% Check whether argument is empty:
%%......................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% Without eTeX-extensions:
%%   The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%%   <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%% With eTeX-extensions:
%\newcommand\UD@CheckWhetherNull[1]{%
%  \romannumeral0\if\relax\detokenize{#1}\relax
%  \expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
%  {\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
%  {\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
%}%
%%----------------------------------------------------------------------
%% Check whether argument contains no exclamation mark which is not nested 
%% in braces:
%%......................................................................
%% \UD@CheckWhetherNoExclam{<Argument which is to be checked>}%
%%                         {<Tokens to be delivered in case that argument
%%                           contains no exclamation mark>}%
%%                         {<Tokens to be delivered in case that argument
%%                           contains exclamation mark>}%
%%
\newcommand\UD@GobbleToExclam{}\long\def\UD@GobbleToExclam#1!{}%
\newcommand\UD@CheckWhetherNoExclam[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@GobbleToExclam#1!}%
}%
%%----------------------------------------------------------------------
%%  \ThisThatFork grabs the first thing behind a
%%  a token-sequence of pattern  !!this!that!
%%......................................................................
\newcommand\ThisThatFork{}
\long\def\ThisThatFork#1!!this!that!#2#3!!!!{#2}%
%%----------------------------------------------------------------------
%% Your \insert-macro - better call it \INSERT because \insert is already
%% defined in LaTeX and I strongly recommend not to override that!
%%......................................................................
\newcommand\INSERT{}%
\long\def\INSERT#1[#2]{%
  \romannumeral0%
  \UD@CheckWhetherNoExclam{#1}{%
    \ThisThatFork!#1!this!that!{ empty macro: #2\\}%<-case #1 is empty/has not okens
                 !!#1!that!{ this macro: #2\\}%<-case #1 = this
                 !!this!#1!{ that macro: #2\\}%<-case #1 = that
                 !!this!that!{ something else macro: #2\\}%<-case #1 = something else without exclamation-mark.
                 !!!!%
  }{ something else macro: #2\\}%<-case #1 = something else with exclamation-mark.
}%
%%----------------------------------------------------------------------
%% Your environment fancyenvironment.
%% fancyenvironments cannot be nested within each other.
%% \end{fancyenvironment} should not be indented.
%%......................................................................    
\newtoks\UD@scratchtoks
\newwrite\UD@scratchwrite
\newenvironment{fancyenvironment}{}{}%
\def\fancyenvironment{%
  \begingroup
  \UD@scratchtoks={}%
  \let\do\@makeother
  \dospecials
  \catcode`\^^M\active
  \def\verbatim@startline{\verbatim@line{}}%
  \def\verbatim@addtoline##1{\verbatim@line\expandafter{\the\verbatim@line##1}}%
  \def\verbatim@processline{%
    \expandafter\UD@CheckWhetherNull\expandafter{\the\verbatim@line}{}%
    {\UD@scratchtoks\expandafter{\the\expandafter\UD@scratchtoks\expandafter\INSERT\the\verbatim@line^^J}}%%
    \def\verbatim@processline{%
      \UD@scratchtoks\expandafter{\the\expandafter\UD@scratchtoks\expandafter\INSERT\the\verbatim@line^^J}%
    }%
  }%
  \def\verbatim@finish{\expandafter\UD@CheckWhetherNull\expandafter{\the\verbatim@line}{}{\verbatim@processline}}%
  \verbatim@
}%
\def\endfancyenvironment{%
  % With eTeX-extensions:
  %   \expandafter\endgroup\expandafter\scantokens\expandafter{\the\expandafter\UD@scratchtoks\@percentchar}%
  % Without eTeX-extensions::
     \expandafter\endgroup
     \expandafter\begingroup
     \expandafter\UD@scratchtoks\expandafter{\the\expandafter\UD@scratchtoks\@percentchar}%
     \immediate\openout\UD@scratchwrite \jobname.scratchwrite %
     \immediate\write\UD@scratchwrite{\the\UD@scratchtoks}%
     \immediate\closeout\UD@scratchwrite
     \endgroup
     \input\jobname.scratchwrite %
}%


\makeatother

\begin{document}
\noindent\verb|\INSERT [test with nothing]|:\\
\INSERT [test with nothing]
\bigskip

\noindent\verb|\INSERT this[test with this]|:\\
\INSERT this[test with this]
\bigskip

\noindent\verb|\INSERT that[test with that]|:\\
\INSERT that[test with that]
\bigskip

\noindent\verb|\INSERT something else[test with something else without !]|:\\
\INSERT something else[test with something else without !]
\bigskip

\noindent\verb|\INSERT !something else![test with something else with !]|:\\
\INSERT !something else![test with something else with !]
\bigskip

\begin{verbatim}
\noindent\begin{fancyenvironment}
this[test]
that[test]
\end{fancyenvironment}
\end{verbatim}

\noindent\begin{fancyenvironment}
this[test]
that[test]
\end{fancyenvironment}

\end{document}

在此处输入图片描述

不要被这个\romannumeral0...东西吓到:

这只是一个让扩展持续进行直至获得所需结果的技巧:

\romannumeral将数字转换为小写罗马符号。

通过0跟踪\romannumeral,可以确保 (La)TeX 已经找到一个数字,并且现在继续搜索更多数字。
在为 寻找更多数字时\romannumeral,(La)TeX 将继续扩展可扩展标记。
在为 寻找更多数字\romannumeral并由此扩展可扩展标记时,(La)TeX 将在数字序列的末尾取一个空格标记,然后默默地删除该空格标记并停止搜索数字。
当 (La)TeX 以这种方式收集到数字的所有数字后,它会将该数字转换为小写罗马符号。但只有在找到的数字为正数时才会这样做。否则它只会吞下该数字,而不传递任何标记。因此,只要您确保在扩展工作之后找到一个非正数(例如 数字),
您就可以(滥用)使用\romannumeral来触发大量扩展工作。0

在讨论中如何知道附加到 csname 宏时的 expandafter 数量?我还尝试通过 详细说明扩展触发\romannumeral

相关内容