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