我读到过关于分隔宏的内容这个答案。实际上,我想做类似以下的事情:
\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]{...}
你是要求紧接着;然后\foo
是this[
参数#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
,这是一个非常糟糕的想法,因为figure
、table
和\marginpar
将\footnote
停止工作并引发非常奇怪的错误。
答案2
TeX 宏实际上不能像这样“重载”。
\def\myinsert this[#1]{%
this macro: #1\\
}
\def\myinsert that[#1]{%
that macro: #1\\
}
在这两种情况下,您都会定义一个名为 的宏\myinsert
。这两个定义之间的唯一区别在于它们如何获取参数。由于只能有一个名称为 的宏,因此第二个定义将覆盖第一个定义,并且从现在开始\myinsert
LaTeX 将期望\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
。