检测下一个字符的 catcode?

检测下一个字符的 catcode?

我只使用 lualatex,但我怀疑这是一个普遍性问题,与核心 TeX 有关。

有没有这样的东西\ifnextcatcode?伪代码:

\def\wanted{\ifnextcatcode{12}{\dothis}{\dothat}}

这个想法是让\wanted命令检测下一个字符是否有 catcode 12,然后分支。可能需要吞噬空间(或不需要)。

这只是文本,不是数学,不是表格,不是列表,不是图表。

虽然\@ifnextchar存在,但可能的字符太多,无法逐一检测。但检测 catcode 可以很好地将它们分组。

编辑:我为什么问这个问题。我查看了该luaquotes软件包,但存在一些问题。从我对其代码的了解来看,这些问题很难解决,即使是比我写得更好的人也很难解决。

我自己的经验是,由于习惯,我的文本中会出现杂乱无章的内容"。它会被排版为右弯引号,这通常是错误的。这是一个众所周知的问题。错误很难被发现。

我不需要"数学运算,也不需要调用 Unicode 值,也不需要 Babel 简写。所以我想,我可能会禁用它tlig,创建"一个活动字符,然后根据后面的字符是否是字母(或少数其他字符,如反引号或左单引号)来决定要做什么。弯曲的左双引号或右双引号将被排版。

初步实验表明,对于英语来说,这可能有效。目前,我将其"激活,然后在每次使用时进行计数器计数。日志文件告诉我在文档正文中使用了多少次(理想情况下,没有)。然后我可以编辑纯文本源文件,并手动替换引号。

答案1

由于您使用的是 LaTeX,因此可以使用expl3。您没有提供代码,因此很难对其进行定制以达到最佳效果,但这里有一个例子。

\documentclass{article}
\begin{document}
\ExplSyntaxOn
\cs_new:Nn \rallg_tester:
{
  \peek_remove_spaces:n
  {
    \peek_catcode:NTF \c_catcode_other_token
    {
      \rallg_dothis:n
    }{
      \rallg_dothat:n
    }
  }
}
\cs_new_eq:NN \rallgwanted \rallg_tester:
\cs_new_protected_nopar:Nn \rallg_dothis:n { #1 ~ is ~ other}
\cs_new_protected_nopar:Nn \rallg_dothat:n { #1 ~ is ~ not ~ other}
\ExplSyntaxOff
\rallgwanted   0
\rallgwanted  1
\rallgwanted  ABC
\end{document}

使用 expl3 进行测试的输出

l3token有了更多信息,可能更倾向于使用 提供的其他测试之一。您可以在 中找到详细信息interface3.pdf。例如,如果您不想占用空格,则有一个替代函数不会占用空格。

答案2

由于您已经有了 expl3 答案但标记了这个 tex-core,我将使用 tex 原语和纯 tex 测试文件来展示这一点:

在此处输入图片描述


\def\wanted{\futurelet\tmp\xwanted}
\def\xwanted{\ifcat.\tmp[is12]\else[isnot12]\fi}


aaa \wanted a  or \wanted (x).

\bye

请注意,如果您有一个以 catcode 12 个字符开头的可扩展宏,则上述操作将出错(另一个分支是安全的)

\def\oops{(abc)}
aaa \wanted a  or \wanted (x)  \wanted \oops.

给出

在此处输入图片描述

更安全的版本是


\def\wanted{\futurelet\tmp\xwanted}
\def\xwanted{\ifcat.\expandafter\xwantedstop\tmp x\xwantedstop[is12]\else[isnot12]\fi}
\def\xwantedstop#1#2\xwantedstop{#1}
\def\oops{(abc)}

aaa \wanted a  or \wanted (x).

aaa \wanted a  or \wanted (x)  \wanted \oops.

\bye

在此处输入图片描述

答案3

此答案的重点不是提前查看标记流的下一个标记。
此答案的重点是找出未定界宏参数的第一个标记/在{...}未定界参数由嵌套在花括号之间的多个标记组成的情况下找出内部的第一个标记。

由于答案的字符数限制为 30,000 个,我需要将答案分成两部分。

这是我的回答的第一部分。

我的回答第一部分包含 TeX 工作原理以及第 2 部分中提供的代码/工作示例的一般解释。

我的回答第二部分保存工作示例。

如果您想点赞,请只点赞我答案的一部分。这可以防止不公平的声誉获取。
如果您想点踩,请点踩我答案中您想踩的任何部分。


一般考虑:

请注意,有 16 个类别代码:

0 = 转义字符,通常为 \ 。1
= 开始分组,通常为 { 。2
= 结束分组,通常为 } 。3
= 数学移位,通常为 $ 。4
= 对齐制表符,通常为 & 。5
= 行尾,通常为 <回车> 。6
= 参数,通常为 # 。7
= 上标,通常为 ^ 。8
= 下标,通常为 _ 。9
= 忽略字符,通常为 <null> 。10
= 空格,通常为 <space> 和 <水平制表符> 。11
= 字母,通常只包含字母 a,...,z 和 A,...,Z。这些字符可用于命令名称。12
= 其他,通常是其他类别中未列出的所有其他字符。13
= 活动字符,例如 ~ 。14
= 注释字符,通常为 %。15
= 无效字符,通常为 <delete> 。

其中,只有 1、2、3、4、6、7、8、10、11、12 和 13 可以归入字符标记类别。

请注意,隐式和显式字符标记都有类别。

例如,之后

\let\egroup=}

控制字标记\egroup是类别 2(结束组)的隐式字符标记,并
\ifcat }\egroup same\else different\fi
产生same

\ifcat不区分显式和隐式字符标记。

因此,本答案的第二部分不区分显式和隐式字符标记。

第 13 类需要特别注意:

\ifcat通常会触发后续可扩展标记的扩展,直到获得两个不可扩展标记,然后可以进行类别比较。

如果活动字符 via\let等于不可扩展标记,\ifcat则将其视为不可扩展标记。

如果活动字符未定义或者通过\let\def转变为可扩展标记,则会导致在比较中 TeX 假设其为类别 13。\ifcat\noexpand⟨(expandable or undefined) active character⟩...\ifcat

因此,\ifcat您可以使用 生成未定义/可扩展的活动字符,因为当通过\noexpandTeX 在\ifcat-comparison 中阻止它们扩展时,不会假设它们与 freeze- 属于同一类别\relax:当通过 阻止它们扩展时,\noexpand它们被认为是类别 13,而 freeze-\relax被认为是类别 16。类别 16 是 TeX 的内部方式,表示它不是具有字符类别的标记。

但随着\ifcat

  • 您无法区分可扩展的活动字符标记与未定义的活动字符标记。

  • 您无法区分表示不可扩展非字符的(不可扩展)活动字符与非隐式字符的不可扩展或\noexpand可阻止扩展的控制字/符号标记。

  • 您无法区分作为隐式字符的(不可扩展的)活动字符,因此无法将非活动字符标记与同一类别的非活动字符标记(无论是显式的还是隐式的)区分开来。

    为了可扩展地进行此类区分,您需要一种机制,其中第 13 类的每个可能的字符标记都作为分隔参数的分隔符出现。虽然对于传统的 TeX 引擎,其内部字符表示是 8 位 ASCII,只有 256 个代码点,这可能是可行的,但对于基于 XeTeX 和 LuaTeX 的引擎来说,这可能是一个问题,因为这里的内部字符表示是 unicode,有 1114111 个代码点。(当然,对于基于 LuaTeX 的引擎,您可以使用 Lua 后端来检查标记。)

您的场景:

看来您想要一个宏。由于它是一个宏而不是 TeX 的- - -测试\ifnextcatcode之一,我建议给它一个不同的名字,一个不以 开头的名字,以免混淆- - -匹配适用的内容和 - - -匹配不适用的内容。\if..\else\fi\if..\if..\else\fi\if..\else\fi

我可以提供一个例程\CategoryOfArgumentsFirstToken,它处理未分隔的宏参数,并在触发两个扩展步骤后返回一个数字标记序列,表示参数的第一个标记的类别。您可以将其与一起使用\ifnum。(或通过分隔参数进行分叉。)

上面我用作\egroup示例,因为无分隔符参数的显式括号必须匹配,因此第 2 类标记只能是宏参数的第一个标记(以防它是隐式的)。

某些情况需要特别注意:

  • 该论点毫无意义。
  • 该参数的第一个标记为类别 1(开始组)的明确字符。
  • 该参数的第一个标记是类别 10(空间)的明确字符。
  • 使用括号黑客技术将\ifcat类别 1 和 2 的测试放入宏中,其中括号必须平衡。
  • 参数的第一个标记是未定义的活动字符或可扩展的活动字符。
    在例程中本答案的第二部分freeze-\relax用于测试不可扩展的控制序列,因为 freeze-\relax不能重新定义为隐式字符标记或 \outer-token。

参数的第一个标记是未定义的控制序列(即,活动字符标记或控制字标记或控制符号标记),这不是问题,因为 -tests\ifcat用于\noexpand防止扩展。

本答案的第二部分您会发现一个产生以下数字序列(没有尾随空格)的例程:\CategoryOfArgumentsFirstToken{⟨argument⟩}

1 = 参数的第一个标记属于类别 1(开始分组)。2
= 参数的第一个标记属于类别 2(结束分组)。3
= 参数的第一个标记属于类别 3(数学移位)。4
= 参数的第一个标记属于类别 4(对齐制表符)。6
= 参数的第一个标记属于类别 6(参数)。7
= 参数的第一个标记属于类别 7(上标)。8
= 参数的第一个标记属于类别 8(下标)。10
= 参数的第一个标记属于类别 10(空格)。11
= 参数的第一个标记属于类别 11(字母)。12
= 参数的第一个标记属于类别 12(其他)。13
= 参数的第一个标记是未定义的活动字符标记或表示可扩展控制序列的活动字符标记。
16 = 参数的第一个标记要么是控制字/符号标记,不表示隐式字符,要么是表示不可扩展控制字/符号标记的活动字符标记。17
= 由于参数为空,因此没有第一个标记。

您可以将其用于\ifnum以下测试:

\ifnum\CategoryOfArgumentsFirstToken{!bla}=12 %
  Argument has a first token whose category is 12(other).
\else
  Argument does not have a first token whose category is 12(other).
\fi

这 ⟨争论⟩可以由一个控制序列组成,该控制序列是在通过或/\CategoryOfArgumentsFirstToken向前窥视标记流的下一个标记的过程中产生的。\futurelet\@ifnextchar\kernel@ifnextchar

这里\futurelet用于预览下一个标记:

\newcommand\wanted[1]{%
  \edef\scratchnum{\unexpanded{#1}}%
  \futurelet\@let@token\wantedinner
}%
\newcommand*\wantedinner{%
  \ifnum\CategoryOfArgumentsFirstToken{\@let@token}=\numexpr(\scratchnum)\relax
    Argument has a first token whose category is \the\numexpr(\scratchnum)\relax.
  \else
    Argument does not have a first token whose category is \the\numexpr(\scratchnum)\relax.
  \fi
}%

\wanted{12}$

\wanted{12}A

\wanted{11}A

此处\@ifnextchar/\kernel@ifnextchar将下一个非空格标记的含义复制到\@let@token,用于提前查看下一个非空格标记:

\newcommand\wanted[1]{%
  \edef\scratchnum{\unexpanded{#1}}%
  \@ifnextchar{\relax}{\wantedinner}{\wantedinner}%
}%
\newcommand*\wantedinner{%
  \ifnum\CategoryOfArgumentsFirstToken{\@let@token}=\numexpr(\scratchnum)\relax
    Argument has a first token whose category is \the\numexpr(\scratchnum)\relax.
  \else
    Argument does not have a first token whose category is \the\numexpr(\scratchnum)\relax.
  \fi
}%

\wanted{12}   $

\wanted{12}   A

\wanted{11}   A

但是,除了已经提到的区分限制之外,您还可以通过\futurelet- 或\@ifnextchar/\kernel@ifnextchar方式不区分标记流中的活动字符和标记流中其他类型的控制序列,因为所\CategoryOfArgumentsFirstToken查看的标记在任何情况下都不是活动字符标记,而是控制字标记\@let@token

除此之外,你会发现

\CategoryOfArgumentsFirstTokenFork{⟨argument⟩}%
  {⟨tokens in case argument has a first token of category 1⟩}%
  {⟨tokens in case argument has a first token of category 2⟩}%
  {⟨tokens in case argument has a first token of category 3⟩}%
  {⟨tokens in case argument has a first token of category 4⟩}%
  {⟨tokens in case argument has a first token of category 6⟩}%
  {⟨tokens in case argument has a first token of category 7⟩}%
  {⟨tokens in case argument has a first token of category 8⟩}%
  {⟨tokens in case argument has a first token of category 10⟩}%
  {⟨tokens in case argument has a first token of category 11⟩}%
  {⟨tokens in case argument has a first token of category 12⟩}%
  {⟨tokens in case argument has a first token which is an undefined active character or an expandable active character⟩}%
  {⟨tokens in case argument has a first token which either is not a character or is an active character that is equal to an unexpandable non-character⟩}%
  {⟨tokens in case argument is empty⟩}%

此例程的用法也可以通过 ⟨ 来应用争论\futurelet⟩ 由一个控制序列组成,该序列是由于通过或提前查看标记流的下一个标记而产生的\@ifnextchar

该例程在内部使用一个例程

\KeepKthOfLArguments{⟨TeX-⟨number⟩-quantity with integer-value K⟩}%
                    {⟨TeX-⟨number⟩-quantity with integer-value L⟩}%list of L undelimited arguments⟩%

如果你愿意,你可以使用该例程并在⟨TeX-⟨number⟩-具有整数值 K 的数量⟩-参数用于\CategoryOfArgumentsFirstTokenFork组合某些情况,例如:

\KeepKthOfLArguments{%
  \CategoryOfArgumentsFirstTokenFork{⟨argument⟩}%
    {1}% category 1
    {1}% category 2
    {2}% category 3
    {3}% category 4
    {3}% category 6
    {2}% category 7
    {2}% category 8
    {4}% category 10
    {5}% category 11
    {5}% category 12
    {6}% expandable active character or undefined active character
    {6}% non-character or active character equal to unexpandable non-character
    {6}% empty
}{6}%
{The first token of the argument is some brace, probably implicit.}%
{The first token of the argument is something that is interesting when doing maths.}%
{The first token of the argument is an alignment tab or a parameter.}%
{The first token of the argument is an explicit space token.}%
{The first token of the argument is a letter or an other character.}%
{The argument does not have a first token which is a nice character token.}%

因此你需要子程序来

  • 测试一个参数是否为空;\UD@CheckWhetherNull在示例中本答案的第二部分
  • 测试参数的第一个标记是否是类别 1 的显式字符标记;\UD@CheckWhetherBrace在示例中本答案的第二部分
  • 测试参数的第一个标记是否是显式空格标记;\UD@CheckWhetherLeadingExplicitSpace在示例中本答案的第二部分
  • 如果参数不为空且第一个标记不是空格或括号,则提取参数的第一个标记;\UD@ExtractFirstArg在示例中本答案的第二部分
  • 选择 L 个参数中的第 K 个;\KeepKthOfLArguments在示例中本答案的第二部分
  • 执行\ifcat测试并选择两个后续参数中的第一个或第二个;\UD@CheckWhetherCategoriesEqual在示例中本答案的第二部分

中的示例本答案的第二部分是 LaTeX 2ε,但您可以通过代替\def\newcommand省略\@ifdefinable-tests 轻松地将其转换为 Knuthian-TeX。

答案4

此答案的重点不是提前查看标记流的下一个标记。
此答案的重点是找出未定界宏参数的第一个标记/在{...}未定界参数由嵌套在花括号之间的多个标记组成的情况下找出内部的第一个标记。

由于答案的字符数限制为 30,000 个,我需要将答案分成两部分。

这是我的回答的第二部分。

我的回答第一部分包含 TeX 工作原理以及第 2 部分中提供的代码/工作示例的一般解释。

我的回答第二部分保存工作示例。

如果您想点赞,请只点赞我答案的一部分。这可以防止不公平的声誉获取。
如果您想点踩,请点踩我答案中您想踩的任何部分。


\errorcontextlines=10000
\makeatletter
%%=============================================================================
%% PARAPHERNALIA:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond, \UD@Exchange,
%% \UD@removespace, \UD@stopromannumeral, \UD@CheckWhetherNull,
%% \UD@CheckWhetherBrace, \UD@CheckWhetherLeadingExplicitSpace,
%% \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@removespace{\UD@Exchange{ }{\def\UD@removespace}{}}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% 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>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
  \expandafter\UD@stopromannumeral\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 a leading
%%                        explicit catcode-1-character-token>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked does not have a
%%                        leading explicit catcode-1-character-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
  \romannumeral\expandafter\UD@secondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@firstoftwo}{%
  \expandafter\UD@stopromannumeral\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingExplicitSpace{<Argument which is to be checked>}%
%%                                     {<Tokens to be delivered in case <argument
%%                                       which is to be checked> does have a
%%                                       leading explicit space-token>}%
%%                                     {<Tokens to be delivered in case <argument
%%                                       which is to be checked> does not have a
%%                                       a leading explicit space-token>}%
\newcommand\UD@CheckWhetherLeadingExplicitSpace[1]{%
  \romannumeral\UD@CheckWhetherNull{#1}%
  {\expandafter\UD@stopromannumeral\UD@secondoftwo}%
  {%
    % Let's nest things into \UD@firstoftwo{...}{} to make sure they are nested in braces
    % and thus do not disturb when the test is carried out within \halign/\valign:
    \expandafter\UD@firstoftwo\expandafter{%
      \expandafter\expandafter\expandafter\UD@stopromannumeral
      \romannumeral\expandafter\UD@secondoftwo
      \string{\UD@CheckWhetherLeadingExplicitSpaceB.#1 }{}%
    }{}%
  }%
}%
\@ifdefinable\UD@CheckWhetherLeadingExplicitSpaceB{%
  \long\def\UD@CheckWhetherLeadingExplicitSpaceB#1 {%
    \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
    {\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
    {\expandafter\expandafter\expandafter\UD@stopromannumeral
     \expandafter\expandafter\expandafter}%
     \expandafter\UD@secondoftwo\expandafter{\string}%
  }%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%%   \UD@ExtractFirstArg{ABCDE} yields  {A}
%%
%%   \UD@ExtractFirstArg{{AB}CDE} yields  {AB}
%%
%% Due to \romannumeral-expansion the result is delivered after two 
%% expansion-steps/after "hitting" \ExtractFirstArg with \expandafter
%% twice.
%%
%% \UD@ExtractFirstArg's argument must not be blank.
%%
%% Use frozen-\relax as delimiter for speeding things up.
%% I chose frozen-\relax because David Carlisle pointed out in
%% <https://tex.stackexchange.com/a/578877>
%% that frozen-\relax cannot be (re)defined in terms of \outer and cannot be
%% affected by \uppercase/\lowercase.
%%
%% \ExtractFirstArg's argument may contain frozen-\relax:
%% The only effect is that internally more iterations are needed for
%% obtaining the result.
%%
%%.............................................................................
\@ifdefinable\UD@RemoveTillFrozenrelax{%
  \expandafter\expandafter\expandafter\UD@Exchange
  \expandafter\expandafter\expandafter{%
  \expandafter\expandafter\ifnum0=0\fi}%
  {\long\def\UD@RemoveTillFrozenrelax#1#2}{{#1}}%
}%
\expandafter\UD@PassFirstToSecond\expandafter{%
  \romannumeral\expandafter
  \UD@PassFirstToSecond\expandafter{\romannumeral
    \expandafter\expandafter\expandafter\UD@Exchange
    \expandafter\expandafter\expandafter{%
    \expandafter\expandafter\ifnum0=0\fi}{\UD@stopromannumeral#1{}}%
  }{%
    \UD@stopromannumeral\romannumeral\UD@ExtractFirstArgLoop
  }%
}{%
  \newcommand\UD@ExtractFirstArg[1]%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  {\UD@stopromannumeral#1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillFrozenrelax#1}}%
}%
%%=============================================================================
%% Check whether categories of two tokens are equal.
%% Expansion of #1 is to yield a single unexpandable token.
%% #2 is to be a single token whose expansion will be prevented.
\newcommand\UD@CheckWhetherCategoriesEqual[2]{%
  \expandafter\UD@firstoftwo\expandafter{%
    \romannumeral
    \ifcat#1\noexpand#2%
    \expandafter\UD@stopromannumeral\expandafter\UD@firstoftwo\else
    \expandafter\UD@stopromannumeral\expandafter\UD@secondoftwo\fi
  }{}%
}%
%%=============================================================================
%% Keep only the K-th of L consecutive undelimited arguments.
%%   ( IF K < 1 OR K > L just remove L consecutive undelimited arguments.
%%     IF L < 1 do nothing. )
%%
%% \KeepKthOfLArguments{<TeX-<number>-quantity K>}%
%%                     {<TeX-<number>-quantity L>}%
%%                     <sequence of L consecutive undelimited arguments>
%%-----------------------------------------------------------------------------
%% If L < 1 yields nothing.
%% Else:
%%   If K >= 1 and K <= L  yields:
%%     <K-th undelimited argument from <sequence of L consecutive undelimited 
%%      arguments>>
%%   If K < 1 or K > L
%%     (-> there is no K-th argument in the
%%         <sequence of L consecutive undelimited arguments> )
%%   yields nothing  but removal of <sequence of L consecutive 
%%          undelimited arguments>
%%.............................................................................
\newcommand\KeepKthOfLArguments[2]{%
  \romannumeral
  % #1: <integer number K>
  % #2: <integer number L>
  \expandafter\UD@KeepKthOfLArgumentsKSmallerOneFork
  \expandafter{\romannumeral\number\number#1 000\expandafter}%
  \expandafter{\romannumeral\number\number#2 000}%
}%
%%-----------------------------------------------------------------------------
\newcommand\UD@KeepKthOfLArgumentsKSmallerOneFork[2]{%
  % #1: <K letters m>
  % #2: <L letters m >
  \UD@CheckWhetherNull{#1}{% K is smaller than one:
    \UD@KeepKthOfLArgumentsRemoveNArguments{#2}{\UD@stopromannumeral}{}%
  }{% K is not smaller than one:
    \expandafter\UD@PassFirstToSecond
    \expandafter{%
      \UD@firstoftwo{}#1%
    }{%
      \UD@KeepKthOfLArgumentsEvaluateLMinusKDifferenceLoop{#1}{#2}%
    }{#2}%
  }%
}%
%%-----------------------------------------------------------------------------
\newcommand\UD@KeepKthOfLArgumentsEvaluateLMinusKDifferenceLoop[4]{%
  % #1: <K letters m>  
  % #2: <L letters m>
  % (For detecting whether K>L or K<=L, during the loop letters m will
  %  be removed both from #1 and #2 until at least one of these arguments 
  %  is empty.
  %  When the loop terminates with 0<K<=L, #1 will be empty and #2
  %  will hold an amount of letters m corresponding to the the 
  %  difference L-K.
  %  When the loop terminates with K>L, #1 will not be empty and #2
  %  will be empty.
  % )
  % #3: <K-1 letters m>
  % #4: <L letters m>
  % (#3 and #4 will be left untouched during the loop so they can be 
  %  used for performing appropriate action when loop terminates as
  %  it is known whether K>L.)
  \UD@CheckWhetherNull{#1}{% We have K<=L:
     \UD@KeepKthOfLArgumentsRemoveNArguments{%
       #3%
      }{%
       \UD@KeepKthOfLArgumentsRemoveNArguments{#2}{\UD@stopromannumeral}%
      }{}%
  }{%
    \UD@CheckWhetherNull{#2}{% We have K>L:
      \UD@KeepKthOfLArgumentsRemoveNArguments{#4}{\UD@stopromannumeral}{}%
    }{% We don't know yet whether K<=L or K>L, thus remove letters m and 
      % do another iteration:
      \expandafter\UD@PassFirstToSecond
      \expandafter{%
        \UD@firstoftwo{}#2%
      }{%
        \expandafter\UD@KeepKthOfLArgumentsEvaluateLMinusKDifferenceLoop
        \expandafter{%
          \UD@firstoftwo{}#1%
        }%
      }{#3}{#4}%
    }%
  }%
}%
%%-----------------------------------------------------------------------------
%% \UD@KeepKthOfLArgumentsRemoveNArguments{<N letters m>}%
%%                                        {<argument 1>}%
%%                                        {<argument 2>}%
%%                                        <sequence of consecutive 
%%                                         undelimited arguments>
%%.............................................................................
%% Removes the first N undelimited arguments from the <sequence of 
%% consecutive undelimited arguments>, then inserts  
%% <argument 1><argument 2>
%%
%% On the one hand when providing <argument 2> empty, you can use 
%% <argument 1> for nesting calls to \UD@KeepKthOfLArgumentsRemoveNArguments.
%% On the other hand you can provide a <space token> for stopping
%% \romannumeral-expansion as  <argument 1> and have the
%% macro grab the <K-th undelimited argument> from the <sequence of L 
%% consecutive undelimited arguments> as <argument 2>.
%%
\newcommand\UD@KeepKthOfLArgumentsRemoveNArguments[3]{%
  %% #1: <N letters m>  
  %% #2: <Argument 1>   
  %% #3: <Argument 2>
  \UD@CheckWhetherNull{#1}{#2#3}{%
    \UD@firstoftwo{%
      \expandafter\UD@KeepKthOfLArgumentsRemoveNArguments
      \expandafter{%
        \UD@firstoftwo{}#1%
      }{#2}{#3}%
    }%
  }%
}%
%%-----------------------------------------------------------------------------
%% End of code for \KeepKthOfLArguments.
%%=============================================================================
%%
%% Fork according to the category of the first token of a macro argument:
%%
\newcommand\CategoryOfArgumentsFirstTokenFork[1]{%
  \romannumeral\expandafter\UD@secondoftwo
  \KeepKthOfLArguments{%
    \UD@CheckWhetherNull{#1}{13}{% number 17 / argument 13
      \UD@CheckWhetherBrace{#1}{1}{% number 1 / argument 1
        \UD@CheckWhetherLeadingExplicitSpace{#1}{8}{%  number 10 / argument 8
           \expandafter\expandafter\expandafter
           \UD@CategoryOfArgumentsFirstTokenFork\UD@ExtractFirstArg{#1}%
        }%
      }%
    }%
  }{13}%
}%
\begingroup
\catcode`\{=1
\catcode`\}=2
\catcode`\$=3
\catcode`\&=4
\catcode`\#=6
\catcode`\^=7
\catcode`\_=8
\catcode`\ =10
\catcode`\A=11
\catcode`\.=12
\newcommand\UD@CategoryOfArgumentsFirstTokenFork[1]{%
  \endgroup  
  \newcommand\UD@CategoryOfArgumentsFirstTokenFork[1]{%
    \UD@CheckWhetherCategoriesEqual{%
      \expandafter\expandafter\expandafter{%
      \expandafter\UD@firstoftwo\expandafter{\expandafter}\string}%
    }{##1}{%
      1% mumber 1 / argument 1
    }{%
      \UD@CheckWhetherCategoriesEqual{%
        \expandafter\UD@firstoftwo\expandafter{\expandafter}\string{}%
      }{##1}{%
        2% mumber 2 / argument 2
      }{%
        \UD@CheckWhetherCategoriesEqual{$}{##1}{3}{% mumber 3 / argument 3
        \UD@CheckWhetherCategoriesEqual{&}{##1}{4}{% mumber 4 / argument 4
        \UD@CheckWhetherCategoriesEqual{####}{##1}{5}{% mumber 6 / argument 5
        \UD@CheckWhetherCategoriesEqual{^}{##1}{6}{% mumber 7 / argument 6
        \UD@CheckWhetherCategoriesEqual{_}{##1}{7}{% mumber 8 / argument 7
        \UD@CheckWhetherCategoriesEqual{ }{##1}{8}{% % number 10 / argument 8
        \UD@CheckWhetherCategoriesEqual{A}{##1}{9}{% mumber 11 / argument 9
        \UD@CheckWhetherCategoriesEqual{.}{##1}{10}{% mumber 12 / argument 10
        \UD@CheckWhetherCategoriesEqual{#1}{##1}{12}{% mumber 16 / argument 12
          11% mumber 13 / argument 11
        }}}}}}}}}%
      }%
    }%
  }%
}%
\expandafter\expandafter\expandafter\UD@CategoryOfArgumentsFirstTokenFork
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}%
%%-----------------------------------------------------------------------------
%%
%% Get the category of the first token of a macro argument:
%%
\newcommand\CategoryOfArgumentsFirstToken[1]{%
  \romannumeral\expandafter\UD@secondoftwo
  \CategoryOfArgumentsFirstTokenFork{#1}{1}{2}{3}{4}{6}{7}{8}{10}{11}{12}{13}{16}{17}%
}%
\makeatother

\documentclass{article}

\parindent=0pt

\begin{document}

\hrule height 0cm \kern-2cm

\verb*|\CategoryOfArgumentsFirstToken{}|: \CategoryOfArgumentsFirstToken{}

\verb*|\CategoryOfArgumentsFirstToken{\relax dsda \fi}|: \CategoryOfArgumentsFirstToken{\relax dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{\undefined dsda \fi}|: \CategoryOfArgumentsFirstToken{\undefined dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{\fi dsda \fi}|: \CategoryOfArgumentsFirstToken{\fi dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{\endcsname dsda \fi}|: \CategoryOfArgumentsFirstToken{\endcsname dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{\LaTeX dsda \fi}|: \CategoryOfArgumentsFirstToken{\LaTeX dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{\section dsda \fi}|: \CategoryOfArgumentsFirstToken{\section dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{{\relax} dsda \fi}|: \CategoryOfArgumentsFirstToken{{\relax} dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{\egroup dsda \fi}|: \CategoryOfArgumentsFirstToken{\egroup dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{$ dsda \fi}|: \CategoryOfArgumentsFirstToken{$ dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{& dsda \fi}|: \CategoryOfArgumentsFirstToken{& dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{# dsda \fi}|: \CategoryOfArgumentsFirstToken{# dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{^ dsda \fi}|: \CategoryOfArgumentsFirstToken{^ dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{_ dsda \fi}|: \CategoryOfArgumentsFirstToken{_ dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{  dsda \fi}|: \CategoryOfArgumentsFirstToken{  dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{A  dsda \fi}|: \CategoryOfArgumentsFirstToken{A  dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{.  dsda \fi}|: \CategoryOfArgumentsFirstToken{.  dsda \fi}

\bigskip

Test with implicit characters:

\begin{verbatim}
\def\foo#1{#1}
\let\implicitDollar=$
\let\implicitAnd=&
\let\implicitHash=#
\let\implicitHat=^
\let\implicitUnderscore=_
\foo{\let\implicitSpace= } %
\let\implicitA=A
\let\implicitDot=.
\end{verbatim}

\def\foo#1{#1}
\let\implicitDollar=$
\let\implicitAnd=&
\let\implicitHash=#
\let\implicitHat=^
\let\implicitUnderscore=_
\foo{\let\implicitSpace= } %
\let\implicitA=A
\let\implicitDot=.

\verb*|\CategoryOfArgumentsFirstToken{\bgroup dsda \fi}|: \CategoryOfArgumentsFirstToken{\bgroup dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{\egroup dsda \fi}|: \CategoryOfArgumentsFirstToken{\egroup dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{\implicitDollar dsda \fi}|: \CategoryOfArgumentsFirstToken{\implicitDollar dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{\implicitAnd dsda \fi}|: \CategoryOfArgumentsFirstToken{\implicitAnd dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{\implicitHash dsda \fi}|: \CategoryOfArgumentsFirstToken{\implicitHash dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{\implicitHat dsda \fi}|: \CategoryOfArgumentsFirstToken{\implicitHat dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{\implicitUnderscore dsda \fi}|: \CategoryOfArgumentsFirstToken{\implicitUnderscore dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{\implicitSpace dsda \fi}|: \CategoryOfArgumentsFirstToken{\implicitSpace dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{\implicitA  dsda \fi}|: \CategoryOfArgumentsFirstToken{\implicitA  dsda \fi}

\verb*|\CategoryOfArgumentsFirstToken{\implicitDot dsda \fi}|: \CategoryOfArgumentsFirstToken{\implicitDot dsda \fi}

\bigskip Test with active characters:

\begin{verbatim}
\catcode`\W=13 \let W=\section \catcode`\X=13 \let X={
\catcode`\Y=13 \let Y=\hbox \catcode`\Z=13 \let Z=\UndeFinEd
\end{verbatim}

\verb*|\CategoryOfArgumentsFirstToken{W dsda \fi}|:
\begingroup
\catcode`\W=13 \let W=\section
\CategoryOfArgumentsFirstToken{W dsda \fi}
\endgroup

\verb*|\CategoryOfArgumentsFirstToken{X dsda \fi}|:
\begingroup
\catcode`\X=13 \let X={
\CategoryOfArgumentsFirstToken{X dsda \fi}
\endgroup

\verb*|\CategoryOfArgumentsFirstToken{Y dsda \fi}|:
\begingroup
\catcode`\Y=13 \let Y=\hbox
\CategoryOfArgumentsFirstToken{Y dsda \fi}
\endgroup

\verb*|\CategoryOfArgumentsFirstToken{Z dsda \fi}|:
\begingroup
\catcode`\Z=13 \let Z=\UndeFinEd
\CategoryOfArgumentsFirstToken{Z dsda \fi}
\endgroup

\newpage

\begingroup\footnotesize
\begin{verbatim}
\ifnum\CategoryOfArgumentsFirstToken{. dsda \endcsname}=12 %
  The argument `\verb|. dsda \endcsname|' has a first token which is of category 12.
\else
  The argument `\verb|. dsda \endcsname|' does not have a first token which is of category 12.
\fi
\end{verbatim}
\endgroup

\ifnum\CategoryOfArgumentsFirstToken{. dsda \endcsname}=12 %
  The argument `\verb|. dsda \endcsname|' has a first token which is of category 12.
\else
  The argument `\verb|. dsda \endcsname|' does not have a first token which is of category 12.
\fi

\bigskip\hrule\bigskip

\begingroup\footnotesize
\begin{verbatim}
\ifnum\CategoryOfArgumentsFirstToken{Z dsda \endcsname}=12 %
  The argument `\verb|Z dsda \endcsname|' has a first token which is of category 12.
\else
  The argument `\verb|Z dsda \endcsname|' does not have a first token which is of category 12.
\fi
\end{verbatim}
\endgroup

\ifnum\CategoryOfArgumentsFirstToken{Z dsda \endcsname}=12 %
  The argument `\verb|Z dsda \endcsname|' has a first token which is of category 12.
\else
  The argument `\verb|Z dsda \endcsname|' does not have a first token which is of category 12.
\fi

\bigskip\hrule\bigskip

\begingroup\footnotesize
\begin{verbatim}
\ifnum\CategoryOfArgumentsFirstToken{}=12 %
  The argument `\verb||' has a first token which is of category 12.
\else
  The argument `\verb||' does not have a first token which is of category 12.
\fi
\end{verbatim}
\endgroup

\ifnum\CategoryOfArgumentsFirstToken{}=12 %
  The argument `\verb||' has a first token which is of category 12.
\else
  The argument `\verb||' does not have a first token which is of category 12.
\fi

\newpage

\begingroup\footnotesize
\begin{verbatim}
\KeepKthOfLArguments{%
  \CategoryOfArgumentsFirstTokenFork{A Bla}%
    {1}% category 1
    {1}% category 2
    {2}% category 3
    {3}% category 4
    {3}% category 6
    {2}% category 7
    {2}% category 8
    {4}% category 10
    {5}% category 11
    {5}% category 12
    {6}% expandable active character or undefined active character
    {6}% non-character or active character equal to unexpandable non-character
    {6}% empty
}{6}%
{The first token of the argument is some brace, probably implicit.}%
{The first token of the argument is something that is interesting when doing maths.}%
{The first token of the argument is an alignment tab or a parameter.}%
{The first token of the argument is an explicit space token.}%
{The first token of the argument is a letter or an other character.}%
{The argument does not have a first token which is a nice character token.}%
\end{verbatim}
\endgroup

\KeepKthOfLArguments{%
  \CategoryOfArgumentsFirstTokenFork{A Bla}%
    {1}% category 1
    {1}% category 2
    {2}% category 3
    {3}% category 4
    {3}% category 6
    {2}% category 7
    {2}% category 8
    {4}% category 10
    {5}% category 11
    {5}% category 12
    {6}% expandable active character or undefined active character
    {6}% non-character or active character equal to unexpandable non-character
    {6}% empty
}{6}%
{The first token of the argument is some brace, probably implicit.}%
{The first token of the argument is something that is interesting when doing maths.}%
{The first token of the argument is an alignment tab or a parameter.}%
{The first token of the argument is an explicit space token.}%
{The first token of the argument is a letter or an other character.}%
{The argument does not have a first token which is a nice character token.}%

\bigskip\hrule\bigskip

\begingroup\footnotesize
\begin{verbatim}
\KeepKthOfLArguments{%
  \CategoryOfArgumentsFirstTokenFork{! Bla}%
    {1}% category 1
    {1}% category 2
    {2}% category 3
    {3}% category 4
    {3}% category 6
    {2}% category 7
    {2}% category 8
    {4}% category 10
    {5}% category 11
    {5}% category 12
    {6}% expandable active character or undefined active character
    {6}% non-character or active character equal to unexpandable non-character
    {6}% empty
}{6}%
{The first token of the argument is some brace, probably implicit.}%
{The first token of the argument is something that is interesting when doing maths.}%
{The first token of the argument is an alignment tab or a parameter.}%
{The first token of the argument is an explicit space token.}%
{The first token of the argument is a letter or an other character.}%
{The argument does not have a first token which is a nice character token.}%
\end{verbatim}
\endgroup

\KeepKthOfLArguments{%
  \CategoryOfArgumentsFirstTokenFork{! Bla}%
    {1}% category 1
    {1}% category 2
    {2}% category 3
    {3}% category 4
    {3}% category 6
    {2}% category 7
    {2}% category 8
    {4}% category 10
    {5}% category 11
    {5}% category 12
    {6}% expandable active character or undefined active character
    {6}% non-character or active character equal to unexpandable non-character
    {6}% empty
}{6}%
{The first token of the argument is some brace, probably implicit.}%
{The first token of the argument is something that is interesting when doing maths.}%
{The first token of the argument is an alignment tab or a parameter.}%
{The first token of the argument is an explicit space token.}%
{The first token of the argument is a letter or an other character.}%
{The argument does not have a first token which is a nice character token.}%

\bigskip\hrule\bigskip

\begingroup\footnotesize
\begin{verbatim}
\KeepKthOfLArguments{%
  \CategoryOfArgumentsFirstTokenFork{$ Bla}%
    {1}% category 1
    {1}% category 2
    {2}% category 3
    {3}% category 4
    {3}% category 6
    {2}% category 7
    {2}% category 8
    {4}% category 10
    {5}% category 11
    {5}% category 12
    {6}% expandable active character or undefined active character
    {6}% non-character or active character equal to unexpandable non-character
    {6}% empty
}{6}%
{The first token of the argument is some brace, probably implicit.}%
{The first token of the argument is something that is interesting when doing maths.}%
{The first token of the argument is an alignment tab or a parameter.}%
{The first token of the argument is an explicit space token.}%
{The first token of the argument is a letter or an other character.}%
{The argument does not have a first token which is a nice character token.}%
\end{verbatim}
\endgroup

\KeepKthOfLArguments{%
  \CategoryOfArgumentsFirstTokenFork{$ Bla}%
    {1}% category 1
    {1}% category 2
    {2}% category 3
    {3}% category 4
    {3}% category 6
    {2}% category 7
    {2}% category 8
    {4}% category 10
    {5}% category 11
    {5}% category 12
    {6}% expandable active character or undefined active character
    {6}% non-character or active character equal to unexpandable non-character
    {6}% empty
}{6}%
{The first token of the argument is some brace, probably implicit.}%
{The first token of the argument is something that is interesting when doing maths.}%
{The first token of the argument is an alignment tab or a parameter.}%
{The first token of the argument is an explicit space token.}%
{The first token of the argument is a letter or an other character.}%
{The argument does not have a first token which is a nice character token.}%

\bigskip\hrule\bigskip

\begingroup\footnotesize
\begin{verbatim}
\KeepKthOfLArguments{%
  \CategoryOfArgumentsFirstTokenFork{_ Bla}%
    {1}% category 1
    {1}% category 2
    {2}% category 3
    {3}% category 4
    {3}% category 6
    {2}% category 7
    {2}% category 8
    {4}% category 10
    {5}% category 11
    {5}% category 12
    {6}% expandable active character or undefined active character
    {6}% non-character or active character equal to unexpandable non-character
    {6}% empty
}{6}%
{The first token of the argument is some brace, probably implicit.}%
{The first token of the argument is something that is interesting when doing maths.}%
{The first token of the argument is an alignment tab or a parameter.}%
{The first token of the argument is an explicit space token.}%
{The first token of the argument is a letter or an other character.}%
{The argument does not have a first token which is a nice character token.}%
\end{verbatim}
\endgroup

\KeepKthOfLArguments{%
  \CategoryOfArgumentsFirstTokenFork{_ Bla}%
    {1}% category 1
    {1}% category 2
    {2}% category 3
    {3}% category 4
    {3}% category 6
    {2}% category 7
    {2}% category 8
    {4}% category 10
    {5}% category 11
    {5}% category 12
    {6}% expandable active character or undefined active character
    {6}% non-character or active character equal to unexpandable non-character
    {6}% empty
}{6}%
{The first token of the argument is some brace, probably implicit.}%
{The first token of the argument is something that is interesting when doing maths.}%
{The first token of the argument is an alignment tab or a parameter.}%
{The first token of the argument is an explicit space token.}%
{The first token of the argument is a letter or an other character.}%
{The argument does not have a first token which is a nice character token.}%

\end{document}

在此处输入图片描述

相关内容