我正在尝试编写一个在数学模式下工作的宏,如下所示:\mymacro^3 str
产生与相同的结果\pi^3 str
,而\mymacro{str1}{str2}{str3}
产生与相同的结果\pi str1 \pi str2 \pi str3
。换句话说,如果紧随其后的字符是^
,它只会打印π;否则它会吸收一个参数(字符串),打印π和参数,并继续吸收参数,直到下一个字符不是{。
这是我的代码:
\def\mmymacro#1{
\pi #1
\@ifnextchar\bgroup{\mmymacro}{\relax}
}
\def\mymacro{
\@ifnextchar^{\pi}\mmymacro
}
只要它处于显示模式,后面跟着其他字符或处于内联模式,它就可以工作,但是当用作
$$
\mymacro
$$
警告是
! Display math should end with $$.<to be read again> $
! Missing $ inserted.<inserted text>$
我已经添加了\relax
定义,但是它没有解决问题。所以我尝试了另一种方法:
\def\mmymacro#1{
\pi #1
\@ifnextchar\bgroup{\mmymacro}{}
}
\def\mymacro{
\@ifnextchar^{\pi}{\mmymacro}\relax
}
这不再会产生警告,但当像 那样使用时\mymacro{x}{y}
,即没有前导^
,它会产生与 相同的结果\pi \pi x \pi y
,无论是在显示模式还是在内联模式下。我看到沃纳的回答,但根据他的线索,我发现似乎插入了一个额外的 {。有什么解释吗?如何修复?
答案1
这确实违反了所有的 latex 语法准则(没有标准的 latex 命令会采用可变数量的{}
参数),但是
\documentclass{article}
\makeatletter
\def\mmmymacro#1{%%%
\pi#1\mymacro
}
\def\mmymacro{%%%
\@ifnextchar\bgroup{\mmmymacro}{\pi}%%%
}
\def\mymacro{%%%
\@ifnextchar^{\pi}\mmymacro
}
\begin{document}
$\mymacro aaa$
$\mymacro^3 aaa$
$\mymacro{111}{222}{333} aaa$
$$
\mymacro
$$
\end{document}
答案2
以下方法既不\mymacro
执行任何赋值(\@ifnextchar
不使用 → ),也不执行\if..
.. \else
..\fi
构造方面的任何分支。
不需要像 eTeX 这样的扩展\mymacro
。
(但\testsuite
用于测试/展示 行为的 -macro\mymacro
使用 eTeX' \scantokens
。)
\mymacro
无论如何,它本身都会处理一个未限定的宏参数。
如果该参数的内容确实有一个前导左括号,或者确实有一个前导空格标记,后面跟着一个左括号,则假定该参数包含一个未分隔的参数列表,每个参数都用括号括起来。标记\pi
将放在每个参数的前面。括号括起来的参数之间的空格标记将被忽略。
如果该参数的内容既没有前导左括号,也没有前导空格和后导左括号,那么该标记\pi
将被放置在该参数的前面。
\documentclass{article}
\makeatletter
%%=========================================================================
%% Paraphernalia:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@Exchange, \UD@removespace
%% \UD@CheckWhetherNull, \UD@CheckWhetherBrace, \UD@Loopcall
%%=========================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@removespace{}\UD@firstoftwo{\def\UD@removespace}{} {}%
%%----------------------------------------------------------------------
%% 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>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
%%
%% (\romannumeral expansion was introduced in order to overcome the
%% concerns and worries about improperly balanced \if..\else..\fi constructs.)
%%
\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}%
}%
%%----------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%......................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has leading
%% catcode-1-token>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has no leading
%% catcode-1-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
\romannumeral0\expandafter\UD@secondoftwo\expandafter{\expandafter{%
\string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
\UD@firstoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is a
%% space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is not
%% a space-token>}%
\newcommand\UD@CheckWhetherLeadingSpace[1]{%
\romannumeral0\UD@CheckWhetherNull{#1}%
{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
{\expandafter\UD@secondoftwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\newcommand\UD@CheckWhetherLeadingSpaceB{}%
\long\def\UD@CheckWhetherLeadingSpaceB#1 {%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@secondoftwo#1{}}%
{\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
{\UD@Exchange{ }{\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter}\expandafter\expandafter
\expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%-------------------------------------------------------------------------
%% Expandable Loop:
%% \UD@Loopcall{<action>}%
%% {<action if list empty>}%
%% {<preset>}%
%% {{<e_k>}{<e_(k+1)>}..{e_n}}% <- this is the list
%%
%% If list is empty: <action if list empty>
%% Else:
%% <action>{<e_k>}<preset> \UD@Loopcall{<action>}%
%% {<action if list empty>}%
%% {<preset>}{{<e_(k+1)>}..{e_n}}
%%
%% <action> can be defined to mesh into the iteration-process, e.g.,
%% (ex)changing arguments like the <action if list empty>-argument
%% for the next \UD@Loopcall-iteration, e.g., terminating iteration
%% prematurely under some circumstances.
%%
%% Space tokens between elements will be ignored.
%%.........................................................................
\newcommand\UD@RemoveTillUD@nil{}%
\long\def\UD@RemoveTillUD@nil#1#2\UD@nil{{#1}}%
\newcommand\UD@Extractfirstloop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@Exchange{#1}}%
{\expandafter\UD@Extractfirstloop\expandafter{\UD@RemoveTillUD@nil#1}}%
}%
\newcommand\UD@Loopcall[4]{%
\UD@CheckWhetherNull{#4}{#2}{%
\UD@CheckWhetherLeadingSpace{#4}{%
\expandafter\UD@Exchange
\expandafter{\expandafter{\UD@removespace#4}}%
{\UD@Loopcall{#1}{#2}{#3}}%
}{%
\expandafter\UD@Exchange
\expandafter{\expandafter{\UD@firstoftwo{}#4}}%
{\UD@Extractfirstloop{#4\UD@nil}{#1}#3\UD@Loopcall{#1}{#2}{#3}}%
}%
}%
}%
%%=========================================================================
%% Macros for the user, defined by means of the paraphernalia:
%%
\newcommand\mymacro[1]{\mmymacro{#1}{#1}}%
\newcommand\mmymacro[2]{%
\UD@CheckWhetherLeadingSpace{#1}{%
\expandafter\mmymacro\expandafter{\UD@removespace#1}{#2}%
}{%
\UD@CheckWhetherBrace{#1}{%
\UD@Loopcall{\expandafter\pi\UD@secondoftwo{}}{}{}{#1}%
}{\pi#2}%
}%
}%
\makeatother
%%=========================================================================
%% Macros for the testsuite that is used for testing the behavior of the
%% macros for the user:
%%
\begingroup
\catcode`\X=13\relax
\gdef\Activex{%
\catcode`\X=13\relax
\defX{\,}%
}%
\endgroup
\newcommand\testsuite[1]{%
\begingroup
\def\pi{\string\piX}%
\let\olddospecials\dospecials
\def\dospecials{\olddospecials\Activex}%
\edef\test{$\string\mymacro{#1}$}%
\scantokens\expandafter\expandafter\expandafter{%
\expandafter\string
\expandafter\verb
\expandafter*%
\expandafter|\test|}yields:\\%
\edef\test{$\mymacro{#1}$}%
\scantokens\expandafter\expandafter\expandafter{%
\expandafter\string
\expandafter\verb
\expandafter*%
\expandafter|\test|}yields:\\%
\endgroup
$\mymacro{#1}$
\medskip
}%
\parindent=0ex %
\begin{document}
\testsuite{^3 str}
\testsuite{str}
\testsuite{ str}
\testsuite{{str1}{str2}{str3}}
\testsuite{{^2 str1}{^5 str2}{^{11} str3}}
\testsuite{ {^2 str1} {^5 str2} {^{11} str3} }
\testsuite{}
\hrulefill\null
\verb*|$\mymacro{^3 str}$|:
$\mymacro{^3 str}$
\verb*|$\mymacro{str}$|:
$\mymacro{str}$
\verb*|$\mymacro{ str}$|:
$\mymacro{ str}$
\verb*|$\mymacro{{str1}{str2}{str3}}$|:
$\mymacro{{str1}{str2}{str3}}$
\verb*|$\mymacro{{^2 str1}{^5 str2}{^{11} str3}}$|:
$\mymacro{{^2 str1}{^5 str2}{^{11} str3}}$
\verb*|$\mymacro{ {^2 str1} {^5 str2} {^{11} str3} }$|:
$\mymacro{ {^2 str1} {^5 str2} {^{11} str3} }$
\verb*|$\mymacro{}$|:
$\mymacro{}$
\end{document}