在 LaTeX3 中,我可以定义一个函数来将标记列表与字符串进行比较
\cs_generate_variant:Nn \tl_if_eq:nnTF { V }
\cs_new_nopar:Npn \module_compare:n #1
{
\tl_if_eq:VnTF \g_some_tl { #1 } { 1 } { 0 }
}
但现在我想做这个功能可穷尽扩展。除此之外还有其他类似的功能\tl_if_eq:nnTF
可以使用吗?
编辑:为了给我的问题提供更多背景信息,我添加了以下不起作用的示例。(请注意,\ifnum
出于某些原因我需要使用它。)
\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\tl_set:Nn \g_some_tl {abc}
\cs_generate_variant:Nn \tl_if_eq:nnTF { V }
\cs_new_nopar:Npn \mycompare #1
{
\tl_if_eq:VnTF \g_some_tl { #1 } { 1 } { 0 }
}
\ExplSyntaxOff
\begin{document}
\def\mytest#1{\ifnum \mycompare{#1} > 0 do some\else do other\fi}
\mytest{uvw}
\mytest{abc}
\end{document}
根据 Joseph Wright 的评论,用 替换\tl_if_eq:VnTF
可以\str_if_eq:VnTF
解决我的问题。
答案1
标记列表比较无法扩展,因为 TeX 比较它们的唯一方法是将它们作为宏的替换文本。
但是,所有引擎(Knuth TeX 除外)都实现了可扩展的“字符串比较”,基本上可以正常工作\detokenize
(细节稍微复杂一些)。
您\usepackage{pdftexcmds}
可以使用(它由XeTeX\pdf@strcmp
调用,并使用 LuaTeX 中的 Lua 脚本进行模拟)。代码\pdfstrcmp
pdftex
\strcmp
\pdf@strcmp{<string-A>}{<string-B>}
如果字符串相等则返回零(扩展后,这里涉及到细节),否则返回 1 或 -1。
这也适用于expl3
as \str_if_eq:nn(TF)
(括号表示可以省略一个或两个说明符)或\str_if_eq_x:nn(TF)
。前者不会对其前两个参数执行扩展,而后者会。
就你的情况而言你可以
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\DeclareExpandableDocumentCommand{\mytest}{m}
{
\zh_mytest:n { #1 }
}
\cs_new:Npn \zh_mytest:n #1
{
\str_if_eq:VnTF \g_zh_fixed_tl { #1 } { 1 } { 0 }
}
\cs_generate_variant:Nn \str_if_eq:nnTF { V }
\tl_gclear_new:N \g_zh_fixed_tl
\tl_gset:Nn \g_zh_fixed_tl { abc }
\ExplSyntaxOff
\begin{document}
\edef\test{\ifnum\mytest{x}>0 Equal\else Unequal\fi}
\texttt{\meaning\test}
\edef\test{\ifnum\mytest{abc}>0 Equal\else Unequal\fi}
\texttt{\meaning\test}
\end{document}
的使用\edef
只是为了表明该宏是完全可扩展的。然而,expl3
不建议将这种方式的代码与旧式代码混合使用。
我更喜欢
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\DeclareExpandableDocumentCommand{\mytestTF}{mmm}
{
\zh_mytest:nnn { #1 } { #2 } { #3 }
}
\cs_new:Npn \zh_mytest:nnn #1 #2 #3
{
\str_if_eq:VnTF \g_zh_fixed_tl { #1 } { #2 } { #3 }
}
\cs_generate_variant:Nn \str_if_eq:nnTF { V }
\tl_gclear_new:N \g_zh_fixed_tl
\tl_gset:Nn \g_zh_fixed_tl { abc }
\ExplSyntaxOff
\begin{document}
\edef\test{\mytestTF{x}{Equal}{Unequal}}
\texttt{\meaning\test}
\edef\test{\mytestTF{abc}{Equal}{Unequal}}
\texttt{\meaning\test}
\end{document}
答案2
你可以使用该etl
包。它可以扩展比较标记列表。但它有一些限制:
它忽略组开始和组结束标记的字符代码(通常
{
和}
,但也可以这样做\catcode`\(=1 \catcode`\)=2
然后使用(something)
并且etl
无法将其与区分{something}
)它无法区分一个活跃角色与另一个类别的同一个角色,所以在
\lccode`\~=`\: \lowercase{\let~:} \catcode`\:=13
现在包无法检测是否
:
是活动令牌或者类别 12 之一。空格被规范化(但这发生在 TeX 解析的早期阶段)
正如 Ulrich Diez 在评论(和他的回答)中指出的那样,另一个问题是是否为
\escapechar
负数。简明扼要地总结一下区别etl
:具有相同含义和字符串表示的标记被视为相等。
请参阅以下内容:
\documentclass[]{article}
\usepackage{etl}
\ExplSyntaxOn
\tl_const:Nn \c_my_cmp_tl { some~ {complex}~ token~ \list }
\cs_generate_variant:Nn \etl_if_eq:nnTF { V }
\NewExpandableDocumentCommand \mycompare { m }
{
\etl_if_eq:VnTF \c_my_cmp_tl {#1} { 1 } { 0 }
}
\ExplSyntaxOff
\begin{document}
% completely wrong
\mycompare{Something wrong}
% missing braces
\mycompare{some complex token list}
% missing braces
\mycompare{some complex token \list}
% last not a macro
\mycompare{some {complex} token list}
% last not a macro
\edef\tmp{\noexpand\mycompare{some {complex} token \string\list}}
\tmp
% this is the only correct one
\mycompare{some {complex} token \list}
% macro parameters are no problem
\mycompare{## macro#1 parameters}
% proving it's fully expandable
\edef\tmp{\mycompare{wrong}}\texttt{\meaning\tmp}
% proving it's fully expandable
\edef\tmp{\mycompare{some {complex} token \list}}\texttt{\meaning\tmp}
\end{document}
答案3
我认为完全在 TeX 中进行可扩展的测试(不使用 Lua)是不可行的,因为可靠性要达到 100% —— 至少对于像 XeTeX 或 LuaTeX 这样的 unicode-TeX 引擎来说是不可行的,因为其中可能存在 1114112 种不同的字符代码。
(我认为,如果您愿意花费大量代码来做一些大括号破解的话,处理类别 1/2 的明确字符标记是可行的。)
难题是:
- 找到一种可扩展的方法,例如,当活动挂件等于所讨论的非活动字符标记时,区分显式非活动字符标记与其活动挂件。
例如,
在这些条件下,如何在不使用处理分隔参数的宏的情况下可扩展地区分 active-\begingroup\catcode`\Z=13\def\temp{\endgroup\letZ= }\temp Z
Z
和 letter- ? (在这种情况下,将或应用于active-或 letter-会产生相同的结果;通过/ /将 active-与 letter-进行比较会产生 true-branch。我们还有什么可以可扩展地区分标记的方法?)Z
\string
\meaning
Z
Z
Z
Z
\ifx
\if
\ifcat
- 寻找一种可扩展的方法,例如,区分一个字母控制序列,其名称等于一个明确的字符标记,当为
\escapechar
负数时,该字母控制序列等于该字符标记。
例如,
在这些条件下,如何在不使用处理分隔参数的宏的情况下可扩展地\let\!=!\escapechar=-1
\!
与 other-区分开来? (在这种情况下,将或应用于other-或产生相同的结果;将 other-与via / /进行比较会产生真正的分支。我们还有什么可以可扩展地区分标记的方法?)!
\string
\meaning
!
\!
!
\!
\ifx
\if
\ifcat
我引入了不使用处理分隔参数的宏的限制。
对于每个可能的字符代码,您都可以编写一个宏,通过分隔参数来检测该字符代码的标记是否是该字符代码的活动字符/是否是名称等于该字符代码的字符的单字母控制序列/是否是其他字符代码。
但在 xetex 或 luatex 等 unicode 机器上,从 0 到 1114111 的 1114112 个字符代码都是可能的,因此需要 1114112 个这样的宏。(不能“动态”定义这样的宏,因为定义与可扩展性相矛盾。)
在传统的 8 位引擎上,只能处理 256 个字符代码,因此现在可能可以定义 256 个宏。
通过处理分隔参数的许多宏来产生活动字符和(如果为\escapechar
负数)单字母控制序列的机制概述(如果适用的话仅适用于 8 位引擎):
假设你想检验一个你已经知道的宏观论证
- 不属于类别 1 或 2 的明确字符标记
- 或一个活动字符标记让等于不属于 1 类或 2 类的吊坠
- 或者让单字母控制序列等于不属于类别 1 或 2 的字符标记,其字符代码对应于形成单字母控制序列名称的字符,而为
\esapechar
负数,
即,在任何情况下,不属于类别 1/2 的事物,其字符串化会产生类别 10 或 12 的单个字符标记
\escapechar
,你可以通过类似以下方式扩展输出活动字符和(在极端情况下为负)单字母控制序列:
\long\def\tokenfork#1<token1><token2>#2#3\SEP{#2}%
\long\def\forktoken#1{%
\tokenfork
#1<token2>{tokens in case #1 = <token1>}%
<token1>#1{tokens in case #1 = <token2>}%
<token1><token2>{<tokens in case #1 is s.th. else}%
\SEP
}
其中<token 1>
是活动字符,<token 2>
是单字母控制序列。
而不是和\tokenfork
,\forktoken
你选择字符出现的宏名称,例如\Afork
和\forkA
用于产生活动A
,并且\A
通过其字符代码为\csname fork\string#1\endcsname{#1}
并且其字符串化产生单个字母的东西:#1
A
A
\long\def\firstofthree#1#2#3{#1}%
\long\def\secondofthree#1#2#3{#2}%
\long\def\thirdofthree#1#2#3{#3}%
\long\def\forkdefiner#1{\begingroup\lccode`\X=#1 \lccode`\~=#1 \lowercase{\let~\relax\definefork{X}{~}}}%
\long\def\definefork#1#2{%
\expandafter\let\csname #1fork\endcsname\relax
\expandafter\let\csname fork#1\endcsname\relax
\expandafter\let\csname#1\endcsname\relax
\expandafter\defineforkB\csname #1fork\expandafter\endcsname
\csname fork#1\expandafter\endcsname
\csname#1\endcsname{#2}%
}%
\long\def\defineforkB#1#2#3#4{%
\long\gdef#1##1#3#4##2##3\SEP{##2}%
\long\gdef#2##1{#1##1#4{\thirdofthree}#3##1{\secondofthree}#3#4{\firstofthree}\SEP}%
\endgroup
}%
% In a loop define two macros forming forking mechanism for the character-codes 0..255
% that are possible in traditional 8-bit engines:
\newcount\scratchy
\scratchy=-1
\loop
\ifnum\scratchy<255 %
\advance\scratchy by 1 %
\forkdefiner{\scratchy}%
\repeat
% Now test with letter-A, one-letter-\A, active-A
\message{%
^^J%
\csname fork\string A\endcsname{A}{neither active char nor one-letter-cs}%
{active char}%
{one-letter-cs}%
}%
{\escapechar=-1 \message{%
^^J%
\csname fork\string\A\endcsname{\A}{neither active char nor one-letter-cs}%
{active char}%
{one-letter-cs}%
}}%
{\catcode`\A=13 \message{%
^^J%
\csname fork\string A\endcsname{A}{neither active char nor one-letter-cs}%
{active char}%
{one-letter-cs}%
}}%
% Now test with other-!, one-letter-\!, active-!
\message{%
^^J%
\csname fork\string!\endcsname{!}{neither active char nor one-letter-cs}%
{active char}%
{one-letter-cs}%
}%
{\escapechar=-1 \message{%
^^J%
\csname fork\string\!\endcsname{\!}{neither active char nor one-letter-cs}%
{active char}%
{one-letter-cs}%
}}%
{\catcode`\!=13 \message{%
^^J%
\csname fork\string!\endcsname{!}{neither active char nor one-letter-cs}%
{active char}%
{one-letter-cs}%
}}%
\bye
输出:
$ pdftex test.tex
This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdftex)
restricted \write18 enabled.
entering extended mode
(./test.tex
neither active char nor one-letter-cs
one-letter-cs
active char
neither active char nor one-letter-cs
one-letter-cs
active char )
No pages of output.
Transcript written on test.log.
在检查标记时,无名控制序列(可通过\csname\endcsname
或 通过在 .tex 输入的一行末尾使用转义字符(反斜杠)来生成,而 为\endlinechar
负数)也可能需要通过分隔参数进行特殊处理,因为应用于\string
它会产生<escapechar>csname<escapechar>endcsname
。对于名称为 的控制序列标记,您会得到相同的字符串化csname<escapechar>endcsname
,可通过 生成\csname csname\string\endcsname\endcsname
。存在非常极端的情况,即这两个不同的标记被赋予了相同的含义,因此\string
仅检查含义和 -representation 不足以区分它们。
还有更多奇怪的事情,例如,frozen- \relax
(无法重新定义)与 normal \relax
,...