如何编写对括号敏感的宏?

如何编写对括号敏感的宏?

xparse包中,有一种g类型的参数可以捕获一对 TeX 组标记内的内容。这使得可以定义对和\foo行为不同的命令。我感兴趣的是这种类型的宏是否可以在纯 TeX 中使用(我猜是的),如果可以,如何实现它。我是纯 TeX 的新手,我很感谢对这种宏的工作流程的详细解释。我也很高兴了解其他可能性,例如在 e-TeX 中而不是在纯 TeX 中。\foo{a}\foo a

编辑

只有在阅读答案之后我才意识到乍一看这是不可能的,因为我所想的只是\def\foo#1...(如果使用这种形式确实是不可能的)。

答案1

您可以使用\futurelet

\let\leftbracechar={
\def\foo{%
  \begingroup
  \futurelet\footemp\innerfoo
}%
\def\innerfoo{%
  \expandafter\endgroup
  \ifx\footemp\leftbracechar
  \expandafter\fooatleftbrace
  \else
  \expandafter\fooatnoleftbrace
  \fi
}%
\def\fooatleftbrace#1{Argument in braces is: {\bf #1}}
\def\fooatnoleftbrace#1{Argument without braces is: {\bf #1}}


\foo a

\foo{a}

\bye

在此处输入图片描述

但请注意,这可能会与隐式字符混淆,例如\foo\bgroup huh?...

除此之外,检查仅针对类别代码为 1(开始组)且字符代码等于花括号字符的字符代码的标记(无论是显式字符标记还是隐式字符标记)。对于类别代码为 1(开始组)但字符代码不同的字符标记,检查不起作用。

但你可以实现一个完整的可扩展检查,它会告诉你第一个标记里面宏参数是类别代码 1(开始组)的显式字符标记,无论其字符代码是什么:

%%-----------------------------------------------------------------------------
%% Check whether argument's first token is an explicit catcode-1-character
%%.............................................................................
%% \UDCheckWhetherBrace{<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>}%
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\long\def\UDCheckWhetherBrace#1{%
  \romannumeral0\expandafter\secondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\firstoftwo\expandafter{\expandafter
  \secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
  \firstoftwo}{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
}%

\UDCheckWhetherBrace{Test}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\UDCheckWhetherBrace{{}Test}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\UDCheckWhetherBrace{{Test}}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\leavevmode\hrulefill\null

% Now let's have some fun: Give [ the same functionality as {:
\catcode`\[=\the\catcode`\{

\UDCheckWhetherBrace{Test}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\UDCheckWhetherBrace{[}Test}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\UDCheckWhetherBrace{[Test}}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\leavevmode\hrulefill\null

% Now let's see that the test on explicit characters is not fooled by implicit characters:
\let\bgroup={

\UDCheckWhetherBrace{Test}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\UDCheckWhetherBrace{\bgroup\egroup Test}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\UDCheckWhetherBrace{\bgroup Test\egroup}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\leavevmode\hrulefill\null    

% The test is also not fooled by implicit active characters:
\catcode`\X=13
\let X={

\UDCheckWhetherBrace{Test}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\UDCheckWhetherBrace{X\egroup Test}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\UDCheckWhetherBrace{X Test\egroup}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%


\bye

在此处输入图片描述


为了了解\UDCheckWhetherBrace其工作原理,让我们用不同的换行符和不同的缩进来编写它:

要点是:让参数的第一个标记命中\string,并使用 TeX 捕获大括号平衡参数来找出左大括号或其他东西是否被中和/变成了 catcode-12 序列:

\long\def\UDCheckWhetherBrace#1{%
  \romannumeral0%
  \expandafter
  \secondoftwo%← This is the interesting \secondoftwo.
  \expandafter{%← This is the interesting \secondoftwo's first argument's opening brace.
  \expandafter{%
  \string#1.}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
             %  , which itself must be brace-balanced, had an opening-brace as first token.
  \expandafter\firstoftwo\expandafter{\expandafter
  \secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
  \firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
              %  did not have an opening-brace as first token
  {\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
}%

例如

\UDCheckWhetherBrace{test}{brace}{no brace}%

产量:

\romannumeral0%
\expandafter
\secondoftwo%← This is the interesting \secondoftwo.
\expandafter{%← This is the interesting \secondoftwo's first argument's opening brace.
\expandafter{%
\string test.}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
              %  , which itself must be brace-balanced, had an opening-brace as first token.
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
            %  did not have an opening-brace as first token
{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
{brace}{no brace}%

产量:

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\expandafter
\secondoftwo%← This is the interesting \secondoftwo.
\expandafter{%← This is the interesting \secondoftwo's first argument's opening brace.
\expandafter{%
\string test.}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
              %  , which itself must be brace-balanced, had an opening-brace as first token.
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
            %  did not have an opening-brace as first token
{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
{brace}{no brace}%

产生执行\expandafter-chain的结果,\string从而将短语“test”中的“t”字符串化:

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\secondoftwo%← This is the interesting \secondoftwo.
{%← This is the interesting \secondoftwo's first argument's opening brace.
{%
⟨character t, due to \string now of category code 12 (other)⟩est.}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument
                                                                  %  , which itself must be brace-balanced, had an opening-brace as first token.
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
            %  did not have an opening-brace as first token
{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
{brace}{no brace}%

产生有趣的结果\secondoftwo

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\expandafter\expandafter\firstoftwo{ }{}\secondoftwo%
{brace}{no brace}%

产生执行\expandafter\firstoftwo从而传递括号内的空间:

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\expandafter⟨space token⟩\secondoftwo%
{brace}{no brace}%

收益执行\expandafter\secondoftwo

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
⟨space token⟩no brace

现在\romannumeral找到空格标记并将其丢弃,并中止收集数字。由于现在它只找到了不构成正数的数字“0”,因此它不会默默地传递任何标记:

%\romannumeral expansion done:
no brace

例如

\UDCheckWhetherBrace{{test}}{brace}{no brace}%

产量:

\romannumeral0%
\expandafter
\secondoftwo%← This is the interesting \secondoftwo.
\expandafter{%← This is the interesting \secondoftwo's first argument's opening brace.
\expandafter{%
\string{test}.}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
               %  , which itself must be brace-balanced, had an opening-brace as first token.
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
            %  did not have an opening-brace as first token
{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
{brace}{no brace}%

产量:

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\expandafter
\secondoftwo%← This is the interesting \secondoftwo.
\expandafter{%← This is the interesting \secondoftwo's first argument's opening brace.
\expandafter{%
\string{test}.}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
               %  , which itself must be brace-balanced, had an opening-brace as first token.
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
            %  did not have an opening-brace as first token
{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
{brace}{no brace}%

产生执行\expandafter-chain并\string因此将短语“{test}”中的左花括号/左花括号字符串化:

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\secondoftwo%← This is the interesting \secondoftwo.
{%← This is the interesting \secondoftwo's first argument's opening brace.
{%
⟨character {, due to \string now of category code 12 (other)⟩test}.}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument
                                                                    %  , which itself must be brace-balanced, had an opening-brace as first token.
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
            %  did not have an opening-brace as first token
{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
{brace}{no brace}%

产生有趣的结果\secondoftwo

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
            %  did not have an opening-brace as first token
{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
{brace}{no brace}%

执行\expandafter-chain可得出\string

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\firstoftwo{%
\secondoftwo⟨character }, due to \string now of category code 12 (other)⟩\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
            %  did not have an opening-brace as first token
{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
{brace}{no brace}%

执行结果\firstoftwo

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\secondoftwo⟨character }, due to \string now of category code 12 (other)⟩\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo
{brace}{no brace}%

执行结果\secondoftwo

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo
{brace}{no brace}%

产生执行\expandafter\firstoftwo从而传递括号内的空间:

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\expandafter⟨space token⟩\firstoftwo%
{brace}{no brace}%

收益执行\expandafter\firstoftwo

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
⟨space token⟩brace

现在\romannumeral找到空格标记并将其丢弃,并中止收集数字。由于现在它只找到了不构成正数的数字“0”,因此它不会默默地传递任何标记:

%\romannumeral expansion done:
brace

答案2

从根本上来说,你只需要\futurelet像使用其他任何前瞻性操作一样使用

\def\foo{\futurelet\footoken\fooaux}
\def\fooaux{%
  \ifx\footoken\bgroup
     % Brace group
   \else
     % Something else
   \fi
}

这与其他前瞻情况“看起来不同”的唯一原因是您不能使用显式的{,而是使用隐式标记\bgroup

相关内容