在 Latex3 中,如何测试输入中的所有标记是否验证测试(例如 \token_if_letter 或 \token_if_space)?

在 Latex3 中,如何测试输入中的所有标记是否验证测试(例如 \token_if_letter 或 \token_if_space)?

在 Latex3 中,我想编写一个函数来检查输入的每个标记是否验证一个谓词。

基本上我想要这个命令的多令牌版本:

\NewDocumentCommand{\mychecksingle}{m m m}{
    \bool_if:nTF{\token_if_space_p:N{#1} || \token_if_letter_p:N{#1}}{#2}{#3}
}

看起来是这样的:

\NewDocumentCommand{\mycheckmulti}{m m m}{
    % Returns #2 if \token_if_space_p:N || \token_if_letter_p is true for every token in the input
    % Returns #3 otherwise
}

如何使用 Latex3/expl3 实现这一点?

答案1

你可以通过多种方式映射标记列表并累积结果,这里我生成一个整数表达式,计算有多少项不满足谓词,然后测试最后该值是否为 0

\documentclass{article}

\begin{document}
\ExplSyntaxOn

\cs_new:Npn \vincent_check_one:n #1 {
  \bool_lazy_and:nnTF 
       { \tl_if_single_p:n {#1} }
       { \tl_if_single_token_p:n {#1} }
  {\bool_if:nTF {\token_if_space_p:N #1 || \token_if_letter_p:N #1 } {0+}{1+}}
  {1+}
  }


  \cs_new:Npn \vincent_map_p:nTF #1 {
    \typeout{Debug: \tl_map_function:nN {#1} \vincent_check_one:n   0}
  \int_compare:nTF
  {
    \tl_map_function:nN {#1} \vincent_check_one:n
    0
    =0
    }
}

\NewDocumentCommand\mycheck{}{\vincent_map_p:nTF} 
\ExplSyntaxOff

\mycheck{aa bb ccc}{YES}{NO}

\mycheck{aa b&b ccc}{YES}{NO}


\mycheck{aa {b c}cc}{YES}{NO}

\end{document}

在此处输入图片描述

答案2

如果我们只使用 TeX 基元,那么我们必须\futurelet在循环中运行基元。代码有点麻烦:

\def\mycheck#1{\mycheckA#1\mycheckA}
\def\mycheckA{\futurelet\next\mycheckB}
\def\mycheckB{\ifcat\noexpand\next a\expandafter\mycheckC \else 
              \expandafter\ifx\space\next \expandafter\expandafter\expandafter\mycheckC \e
              \expandafter\expandafter\expandafter\mycheckE\fi\fi}
\def\mycheckC{\afterassignment\mycheckA \let\next= }
\def\mycheckE#1\mycheckA{\ifx\next\mycheckA \mycheckT \else \mycheckF \fi}
\def\mycheckT#1\fi#2#3{\fi#2}
\def\mycheckF#1\fi#2#3{\fi#3}

\mycheck{aa bb ccc}{YES}{NO}

\mycheck{aa b&b ccc}{YES}{NO}

\mycheck{aa {b c}cc}{YES}{NO}

\bye

如果我们使用 OpTeX,那么\nospacefuturelet可以\futurelet使用。这个宏的行为类似于\futurelet,但还有两个特点:它是可扩展的,并且它获取第一个非空间标记。因此,我们不需要检查检查的标记是否为空间:

\def\mycheck#1{\mycheckA:#1\mycheckA}
\def\mycheckA#1{\nospacefuturelet\next\mycheckB}
\def\mycheckB{\ifcat\noexpand\next a\ea\mycheckA \else \ea\mycheckE\fi}
\def\mycheckE#1\mycheckA{\ifx\next\mycheckA \ea\ignoresecond \else \ea\usesecond\fi}

\mycheck{aa bb ccc}{YES}{NO}

\mycheck{aa b&b ccc}{YES}{NO}

\mycheck{aa {b c}cc}{YES}{NO}

\bye

还有另一种方法:测试当前\catcode用于标记化的标记的当前标记(它不必与之前标记化的标记的类别相同)。在 OpTeX 中,我们可以使用\foreach宏:

\def\mycheck#1{\ea\foreach\detokenize{#1}\do
   {\ifnum\catcode`##1=11 \else \ea\mycheckF \fi}\ignoresecond
}
\def\mycheckF#1\ignoresecond{\_getforstack\usesecond}

\mycheck{aa bb ccc}{YES}{NO}

\mycheck{aa b&b ccc}{YES}{NO}

\mycheck{aa {b c}cc}{YES}{NO}

\bye

这里\_getforstack是因为当发现非字母时我们立即离开\foreach循环,然后我们必须关闭 OpTeX 使用的 for 循环堆栈以便\foreach启用\foreach

答案3

由于\tl_map_...函数没有特殊情况组,因此以下用途etl循环遍历您的输入(允许为单个标记、空格和组定义单独的操作):

\documentclass{article}

\usepackage{etl}

\ExplSyntaxOn
\NewExpandableDocumentCommand \mycheckmulti { m m m }
  {
    \etl_act:nnnnn
      \__vincent_checkmulti_aux:nN % single token needs checking
      \use_none:n % ignore spaces
      { \etl_act_break:n \use_iii:nnn } % nested group means false
      {} % we don't need a status
      {#1}
    \use_i:nn {#2} {#3}
  }
\cs_new:Npn \__vincent_checkmulti_aux:nN #1#2
  { \token_if_letter:NF #2 { \etl_act_break:n \use_iii:nnn } }
\ExplSyntaxOff

\begin{document}
\mycheckmulti{is this fine}{yes}{no}
\mycheckmulti{is {t}his fine}{yes}{no}
\mycheckmulti{is this fine?}{yes}{no}
\mycheckmulti{is\space this fine?}{yes}{no}
\end{document}

相关内容