\@ifnextchar 扩展时插入额外的括号

\@ifnextchar 扩展时插入额外的括号

我正在尝试编写一个在数学模式下工作的宏,如下所示:\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}

编译上述示例生成的pdf文件的截图。

相关内容