在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
。