这里将检查以下(我认为是经典的)“迭代器”定义:
\def\zEnd{\zEnd}
\def\zzIterator#1{%
\ifx#1\zEnd
\else
#1%
\expandafter\zzIterator
\fi
}
\def\zIterator#1{\zzIterator#1\zEnd}
\zIterator{Something...}
首先,我见过\def\zEnd{\zEnd}
和\def\zEnd{\zIterator}
使用过,有什么区别(哪一个更好用)?
我被推荐到“迭代 token“以了解如何保留(否则会吞噬/丢失)空格。由于我更喜欢尽可能多地使用纯 TeX,因此我选择了答案是使用\let
。但由于我还必须将一些字符(标记)大写,因此\let
方法要么需要修改(我不知道),要么应该放弃而采用另一种方法。这是我的努力,希望在您的帮助下(当然),确定如何解决这个问题。
我在尝试以自己的方式解决问题时(再次)陷入了死胡同:通过实现一个 switch(计数寄存器),我可以指示迭代中的当前标记是否必须是大写(以便可以立即转换为大写,switch 将被设置在确定要转换哪些标记的条件案例之一内 - 因此需要一个 switch,多个案例),但由于某种原因,switch 的值直到下一次迭代才设置(这太晚了,下一个标记的迭代正在处理前一个标记的 switch,而不是前一个标记的迭代处理它)。这是我的错误努力(更不用说它不保留空格):
\documentclass[margin=5mm,varwidth]{standalone}
\begin{document}
\newcount\zCapSwitch % UPPERCASE SWITCH
\zCapSwitch0 % SET TO FALSE (NO UPPERCASE CONVERSION NEEDED)
\def\zEnd{\zEnd}
\def\zzIterator#1{%
\ifx#1\zEnd
\else
% ------------ %
% OUTPUT CHUNK %
% ------------ %
% CAPITALIZE "s" AND "i"
% SOMETHING IS WRONG HERE
% (COUNTER DOESN'T GET SET UNTIL
% NEXT ITERATION, WHEN IT'S TOO LATE)
\ifx#1s\zCapSwitch1\fi
\ifx#1i\zCapSwitch1\fi
\ifnum\zCapSwitch=1
\uppercase{#1}% IT'S TOO LATE, WE'RE UPPERCASING THE WRONG TOKEN
\zCapSwitch0 % RESET SWITCH (TO FALSE)
\else
#1%
\fi
% ------------ %
\expandafter\zzIterator
\fi
}
\def\zIterator#1{%
\zzIterator#1\zEnd
}
\zIterator{Keep spaces intact!}
\end{document}
欢迎提供您的解决方案(纯 TeX 优先)。
答案1
这是一个完全不同的方法。它与 Joseph 的回答中的方法相同这里,它允许迭代一个标记列表以查找\%
,它与 LaTeX3 内核中用于 的方法相同,用于TL 19 之前的\tl_(upper|lower|mixed)_case:n
模拟类型扩展,当时在大多数引擎(黑暗时代……)中不可用,用于、和其他几个。不用说,这里的很多代码都是函数的副本,因为你排除了这一点。e
\expanded
\tl_count_tokens:n
\tl_reverse
expl3
另外,我猜想从您的代码中,您想要遍历一个标记列表,并将i
和都s
设为大写。我定义了下面的函数来执行此操作,但不太清楚您想要实现什么。
迭代标记列表的问题在于,有时您不能简单地抓取某些内容作为参数。当 TeX 抓取未分隔的参数时,它会 a) 忽略空格,直到第一个非空格标记,并且 b) 如果抓取的参数以 开头{
并以 结尾,则会删除一层括号}
。因此,假设、 、和\def\foo#1{(#1)}
三个都扩展为相同的。这就是您在函数中丢失空格(和组)的地方。\foo A
\foo ␣A
\foo{A}
(A)
\zIterator
要保留空格和组,您需要定义特定的宏来处理空格和组,并定义另一个宏来处理其他(所谓的N
-type)标记。下面的代码定义了(读取:从 复制并expl3
替换_
和)、和。这三个条件函数将标记列表作为参数,并检查标记列表的第一个标记属于上述三种情况中的哪一种。使用这三个条件,您可以执行类似操作并处理整个标记列表。:
@
\tlhead@if@Ntype
\tlhead@if@group
\tlhead@if@space
\ifNtype{<normal>}{\ifgroup{<grouped tokens>}{<space token>}}
下面的代码将定义\zIterator
为的简写\zIterator@loop#1\zIterator@stop
加上一些簿记。\zIterator@loop
将循环遍历标记列表,检查下一个标记的类型,并根据需要使用、或。\zIterator@Ntype
将\zIterator@group
在组内简单地递归调用。将把一个空格从输入移动到输出标记列表。\zIterator@space
\zIterator@group
\zIterator
\zIterator@space
\zIterator@Ntype
将调用 a \zIterator@do@token
,它将以一个标记作为参数,并根据需要对其进行处理。我定义\zIterator@do@token
为检查i
或s
,并将它们变为大写。大写基于哈希表中的查找。对于每个<char>
要大写的字符,都有一个宏\zIterator@upper@<char>@case
可以扩展为该字符的大写版本。您可以使用 定义这些\zIteratorUppercase{<char>}{<upper case form>}
。我没有在大写代码上花太多心思,所以你可能可以对其进行很大的改进。这只是为了证明概念。
使用下面的代码,输出为\tt\zIterator{Keep spaces {\bf (and groups)} intact!}
:
代码如下:
\catcode`\@=11
% Copies of \tl_if_head_is(N_type|group|space):nTF from expl3:
\def\usenoneN#1{}
\def\useINN#1#2{#1}
\def\useIINN#1#2{#2}
\newcount\exp@end \exp@end=0
\long\def\tlhead@if@Ntype#1{%
\ifcat
\iffalse{\fi\tlhead@if@Ntype@?#1 }%
\expandafter\usenoneN
\expandafter{\expandafter{\string#1?}}%
**%
\expandafter\useINN
\else
\expandafter\useIINN
\fi}
\long\def\tlhead@if@Ntype@#1 {%
\if\relax\detokenize\expandafter{\usenoneN#1}\relax^\fi
\expandafter\usenoneN\expandafter{\iffalse}\fi}
\long\def\tlhead@if@group#1{%
\ifcat
\expandafter\usenoneN
\expandafter{\expandafter{\string#1?}}%
**%
\expandafter\useIINN
\else
\expandafter\useINN
\fi}
\long\def\tlhead@if@space#1{%
\romannumeral\iffalse{\fi
\tlhead@if@space@?#1? }}
\long\def\tlhead@if@space@#1 {%
\if\relax\detokenize\expandafter{\usenoneN#1}\relax
\tlhead@if@space@@\useINN
\else
\tlhead@if@space@@\useIINN
\fi
\expandafter\usenoneN\expandafter{\iffalse}\fi}
\def\tlhead@if@space@@#1#2\fi{\fi\expandafter\expandafter\expandafter#1}
% Iterate over the token list:
\def\zIterator@end{\zIterator@end}
\long\def\zIterator#1{\romannumeral%
\zIterator@loop#1\zIterator@end\zIterator@stop{}}
\long\def\zIterator@loop#1\zIterator@stop{%
\tlhead@if@Ntype{#1}
{\zIterator@Ntype}
{\tlhead@if@group{#1}
{\zIterator@group}
{\zIterator@space}}%
#1\zIterator@stop}
% Handling N-type tokens
\long\def\zIterator@Ntype#1{%
\ifx\zIterator@end#1%
\expandafter\zIterator@finish
\fi
\zIterator@do@token{#1}}
% Handling space tokens
\useINN{\long\def\zIterator@space}{} {\zIterator@return{ }}
% Handling grouped tokens
\long\def\zIterator@group#1{%
\expandafter\expandafter\expandafter\zIterator@group@return
\expandafter\expandafter\expandafter{\zIterator{#1}}}
\long\def\zIterator@group@return#1{\zIterator@return{{#1}}}
% Output:
\long\def\zIterator@return#1#2\zIterator@stop#3{%
\zIterator@loop#2\zIterator@stop{#3#1}}
\long\def\zIterator@finish#1\zIterator@stop#2{\exp@end#2}
%
% Hash table-based upper casing:
\long\def\zIterator@do@token#1{%
\ifnum0%
\if s\noexpand#11\fi
\if i\noexpand#11\fi
>0
\expandafter\zIterator@upper@case
\else
\expandafter\zIterator@return
\fi{#1}}
\long\def\zIterator@upper@case#1{%
\expandafter\expandafter\expandafter\zIterator@return
\expandafter\expandafter\expandafter{\csname zIterator@upper@#1@case\endcsname}}
\long\def\zIteratorUppercase#1#2{%
\expandafter\def\csname zIterator@upper@#1@case\endcsname{#2}}
\zIteratorUppercase{s}{S}
\zIteratorUppercase{i}{I}
\tt\zIterator{Keep spaces {\bf (and groups)} intact!}
\bye
并且使用适当的(大约 10 行)样板代码,它可以与 (ε-)INITEX 一起使用,因此它甚至不依赖于纯 TeX,Knuth 应该决定明年对其进行一些彻底的更改 ;-)
由于 Ulrichexpl3
在他的回答中提到,我想我会在这里添加一个实现,仅用于比较(节省了大约 40 行,而且更容易更改大小写):
\input expl3-generic
\ExplSyntaxOn
\quark_new:N \q__zbp_end
% Iterate over the token list:
\cs_new:Npn \zbp_iterator:n #1
{ \exp:w \__zbp_loop:w #1 \q__zbp_end \q__zbp_stop { } }
\cs_new:Npn \__zbp_loop:w #1 \q__zbp_stop
{
\tl_if_head_is_N_type:nTF {#1}
{ \__zbp_N_type:N }
{
\tl_if_head_is_group:nTF {#1}
{ \__zbp_group:n } { \__zbp_space:w }
}
#1 \q__zbp_stop
}
% Handling N-type tokens
\cs_new:Npn \__zbp_N_type:N #1
{
\token_if_eq_meaning:NNT #1 \q__zbp_end { \__zbp_finish:w }
\__zbp_do_token:N #1
}
% Handling space tokens
\exp_last_unbraced:NNo
\cs_new:Npn \__zbp_space:w \c_space_tl { \__zbp_return:n { ~ } }
% Handling grouped tokens
\cs_new:Npn \__zbp_group:n #1
{ \exp_args:NNo \exp_args:No \__zbp_group_back:n { \zbp_iterator:n {#1} } }
\cs_new:Npn \__zbp_group_back:n #1 { \__zbp_return:n { {#1} } }
% Output:
\cs_new:Npn \__zbp_return:n #1 #2 \q__zbp_stop #3
{ \__zbp_loop:w #2 \q__zbp_stop {#3 #1} }
\cs_new:Npn \__zbp_finish:w #1 \q__zbp_stop #2 { \exp_end: #2 }
%
% Hash table-based upper casing:
\cs_new:Npn \__zbp_do_token:N #1
{
\str_case:nnTF {#1}
{
{s}{ }
{i}{ }
}
{ \exp_args:Nf \__zbp_return:n { \tl_upper_case:n {#1} } }
{ \__zbp_return:n {#1} }
}
% Interfaces
\cs_new:Npn \zIterator { \zbp_iterator:n }
\ExplSyntaxOff
\tt\zIterator{Keep spaces {\bf (and groups)} intact!}
\bye
答案2
以下是我的包中的示例tokcycle
(https://ctan.org/pkg/tokcycle) 可以做到这一点。该包的目的是遍历标记列表。
请注意,在 MWE 中,它不仅在空格后大写,而且还忽略中间的 catcode-12 标记等,例如括号、连字符和括号。
\documentclass{article}
\usepackage{tokcycle}
\newcommand\TitleCase[1]{%
\def\capnext{T}
\tokcycle
{\addcytoks{\nextcap{##1}}}
{\processtoks{##1}}
{\addcytoks{##1}}
{\addcytoks{##1\def\capnext{T}}}
{#1}%
\the\cytoks
}
\newcommand\nextcap[1]{%
\edef\tmp{#1}%
\tctestifx{-#1}{\def\capnext{T}}{}%
\tctestifcon{\if T\capnext}%
{\tctestifcon{\ifcat\tmp A}%
{\uppercase{#1}\def\capnext{F}}%
{#1}}%
{#1}%
}
\begin{document}
\TitleCase{%
|here, {\bfseries\today{}, is [my]} really-big-test
(\textit{capitalizing} words).|\par
here, {\bfseries\today{}, is [my]} really-big-test
(\textit{capitalizing} words).
}
\end{document}
以下是 Plain-TeX 版本:
\input tokcycle.tex
\long\def\TitleCase#1{%
\def\capnext{T}
\tokcycle
{\addcytoks{\nextcap{##1}}}
{\processtoks{##1}}
{\addcytoks{##1}}
{\addcytoks{##1\def\capnext{T}}}
{#1}%
\the\cytoks
}
\long\def\nextcap#1{%
\edef\tmp{#1}%
\tctestifx{-#1}{\def\capnext{T}}{}%
\tctestifcon{\if T\capnext}%
{\tctestifcon{\ifcat\tmp A}%
{\uppercase{#1}\def\capnext{F}}%
{#1}}%
{#1}%
}
\TitleCase{%
|here, {\bf today, is [my]} really-big-test
({\it capitalizing} words).|\par
here, {\bf today, is [my]} really-big-test
({\it capitalizing} words).
}
\bye
答案3
你的例程甚至没有逐渐取代的原因s
如下:S
i
I
\ifx#1s\zCapSwitch1\fi
\ifx#1i\zCapSwitch1\fi
如果 TeX 逐个数字字符标记收集了一个数字数字字符标记,它将继续扩展标记。\fi
是可扩展的,因此 TeX 在遇到时不会停止收集数字\fi
。
\zCapSwitch
因此,TeX在进行后续比较时仍然会收集分配数字的数字\ifnum
。
因此,在进行\zCapSwitch
后续比较时,新值尚未分配。\ifnum
只需确保\zCapSwitch
通过使用空格或后面跟着的数字序列来终止赋值\relax
:
\ifx#1s\zCapSwitch1 \fi
\ifx#1i\zCapSwitch1 \fi
TeX 将会把后面的空格1
作为终止数字序列的部分,因此会丢弃它们而不是保留它们并产生水平粘连。
当你这样做时,代码看起来几乎相同,但在某种程度上替代s
并起作用:i
\documentclass[margin=5mm,varwidth]{standalone}
\begin{document}
\newcount\zCapSwitch % UPPERCASE SWITCH
\zCapSwitch0 % SET TO FALSE (NO UPPERCASE CONVERSION NEEDED)
\def\zEnd{\zEnd}
\def\zzIterator#1{%
\ifx#1\zEnd
\else
% ------------ %
% OUTPUT CHUNK %
% ------------ %
% CAPITALIZE "s" AND "i"
%
% YOU NEED SPACES TO TERMINATE DIGIT-SEQUENCES.
%
\ifx#1s\zCapSwitch1 \fi
\ifx#1i\zCapSwitch1 \fi
\ifnum\zCapSwitch=1
\uppercase{#1}%
\zCapSwitch0 %
\else
#1%
\fi
% ------------ %
\expandafter\zzIterator
\fi
}
\def\zIterator#1{%
\zzIterator#1\zEnd
}
\zIterator{Keep spaces intact!}
\end{document}
但空间仍然没有被保留。
原因是它\zzIterator
确实处理了非分隔宏参数。
在收集属于非分隔宏参数的标记时,TeX 始终会删除/移除/丢弃非分隔宏参数之前的显式空格标记。
\zIterator
除此之外,不处理包含大括号组的参数的情况。
这里有一种方法,其中\futurelet
用于“向前查看”下一个标记的含义,并使用\afterassignment
-\let
用于删除含义等于空间标记含义的标记。
这种方法可以保留空间。
\zIterator
但它仍然没有处理包含大括号组的参数的情况:
\documentclass[margin=5mm,varwidth]{standalone}
\begin{document}
\newcount\zCapSwitch % UPPERCASE SWITCH
\zCapSwitch0 % SET TO FALSE (NO UPPERCASE CONVERSION NEEDED)
\def\zEnd{\zEnd}%
\long\def\foo#1{#1}%
\long\def\fot#1#2{#1}%
\long\def\sot#1#2{#2}%
\foo{\let\zzSpace= } %
\def\zzIterator{\futurelet\zzNext\zzSpacefork}%
\def\zzSpacefork{%
\ifx\zzNext\zzSpace
\expandafter\fot
\else
\expandafter\sot
\fi
{ \afterassignment\zzIterator\let\zzNext= }%
{\zzIteratorA}%
}%
\def\zzIteratorA#1{%
\ifx#1\zEnd
\else
% ------------ %
% OUTPUT CHUNK %
% ------------ %
% CAPITALIZE "s" AND "i"
%
% YOU NEED SPACES TO TERMINATE DIGIT-SEQUENCES.
%
\ifx#1s\zCapSwitch1 \fi
\ifx#1i\zCapSwitch1 \fi
\ifnum\zCapSwitch=1
\uppercase{#1}%
\zCapSwitch0 %
\else
#1%
\fi
% ------------ %
\expandafter\zzIterator
\fi
}
\def\zIterator#1{%
\zzIterator#1\zEnd
}
\zIterator{Keep spaces intact!}
\end{document}
\futurelet
处理大括号组是一个有趣的话题,因为让 TeX 通过或实际上“提前查看”下一个标记的含义\let
是不够的:
这样你就可以知道意义下一个标记的含义等于字符标记的含义,即字符代码为 123 的字符标记的含义——123 是TeX 引擎内部字符表示方案中字符的代码点编号——并且类别代码为 1(开始组)。但您无法确定下一个标记是否是
{1
{
明确的即,下一个标记是否是显式标记,或者是{1(begin group)
隐式即,类似于带有 的 -token 。 类别代码 1(开始组)和类别代码 2(结束组)的字符标记的“显式/隐式”确实很重要,因为非分隔宏参数为空或具有前导显式空格标记或由多个标记组成,并且包含参数分隔符的分隔宏参数将嵌套到一对\bgroupcontrol word
\let\bgroup={
明确的类别代码 1(开始组)的字符标记分别属于类别代码 2(结束组),而隐式类别代码 1(开始组)和类别代码 2(结束组)的字符标记将不会被 TeX 作为宏参数的开始或结束的标记。除此之外:有一天,除了字符之外,某人可能会将类别代码 1(开始组)和类别代码 2(结束组)
{
分别}
分配给其他一些字符,然后这些字符将例如在开始/结束本地范围、收集宏观参数以及收集⟨平衡文本⟩{
,在 TeX 中将被等同于分别处理,但由于字符代码不同,其含义不会等于分别}
的含义。{1(begin group)
}2(end group)
我可以提供一个基于扩展的尾部递归例程,分别用\romannumeral0
显式 catcode-11(letter)-character-tokenss
和i
显式 catcode-11(letter)-character-tokens替换其中S
I
- 经过两次“点击”后即可得出结果
\expandafter
。 - 不会发生临时赋值等。例如,不会使用诸如
\afterassignment
/\let
/之类的东西\futurelet
。因此,该例程也可以在扩展上下文中使用,例如在 \csname..\endcsname 中。 - (不匹配)//
\if..
在参数中不要干扰例程,因为例程基于分隔参数,因此根本不使用-tests。\else
\fi
\if..
- 尽管在某些地方该标记
\UDSelDOm
被用作“标记标记”,但该标记可以出现在参数中,因此该参数没有禁用标记。(除了您通常不能\outer
在宏参数中使用标记。) - 该机制可以处理嵌套在花括号中的内容。
- 您无需考虑区分显式括号和/或空格标记与其隐式吊坠的问题。(当通过
\let
或“向前看”下一个标记时\futurelet
,这个问题可能会引起头痛,因为\let
和\futurelet
让您只处理标记的含义,而字符标记的含义不包含有关该字符标记是显式还是隐式字符标记的信息……)
这个程序的一个副作用是它取代了
- 所有显式 catcode 1(开始组)字符标记均由显式花括号开括号字符标记 (
{
) 的 catcode 1(开始组)。 }
所有显式 catcode 2(结束组)字符标记均由catcode 2(结束组)的显式花括号字符标记 ( ) 表示。
通常{
是唯一一个 catcode 为 1(开始组)的字符。
通常}
是唯一一个 catcode 为 2(结束组)的字符。
因此这通常不会有问题。
该例程只是一个例子。您可以通过修改宏和来增强它以替换除s
和之外的字符。如果您这样做,您还需要调整对的调用。i
\UDsiSelect
\UDsiFork
\UDsiFork
\zIteratorTailRecursiveLoop
(这是一堆代码。如果您不坚持使用纯 TeX,可以缩短代码,例如使用 expl3。)
%%
%% As you wished plain TeX, don't use latex/pdflatex but use tex/pdftex for compiling.
%%
%%=============================================================================
%% Paraphernalia:
%% \UDfirstoftwo, \UDsecondoftwo,
%% \UDExchange, \UDPassFirstBehindThirdToSecond
%% \UDremovespace, \UDCheckWhetherNull, \UCheckWhetherBrace,
%% \UDCheckWhetherLeadingSpace, \UDExtractFirstArg
%%=============================================================================
\long\def\UDfirstoftwo#1#2{#1}%
\long\def\UDsecondoftwo#1#2{#2}%
\long\def\UDExchange#1#2{#2#1}%
\long\def\UDPassFirstBehindThirdToSecond#1#2#3{#2{#3}{#1}}%
\UDfirstoftwo{\def\UDremovespace}{} {}%
%%=============================================================================
%% Check whether argument is empty:
%%=============================================================================
%% \UDCheckWhetherNull{<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>}%
%%
%% Due to \romannumeral0-expansion the result is delivered after two
%% expansion-steps/after two "hits" by \expandafter.
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
%%
\long\def\UDCheckWhetherNull#1{%
\romannumeral0\expandafter\UDsecondoftwo\string{\expandafter
\UDsecondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UDsecondoftwo\string}\expandafter\UDfirstoftwo\expandafter{\expandafter
\UDsecondoftwo\string}\UDfirstoftwo\expandafter{} \UDsecondoftwo}%
{\UDfirstoftwo\expandafter{} \UDfirstoftwo}%
}%
%%=============================================================================
%% Check whether argument's first token is a 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>}%
%%
%% Due to \romannumeral0-expansion the result is delivered after two
%% expansion-steps/after two "hits" by \expandafter.
%%
\long\def\UDCheckWhetherBrace#1{%
\romannumeral0\expandafter\UDsecondoftwo\expandafter{\expandafter{%
\string#1.}\expandafter\UDfirstoftwo\expandafter{\expandafter
\UDsecondoftwo\string}\UDfirstoftwo\expandafter{} \UDfirstoftwo}%
{\UDfirstoftwo\expandafter{} \UDsecondoftwo}%
}%
%%=============================================================================
%% Check whether brace-balanced argument's first token is an explicit
%% space token
%%=============================================================================
%% \UDCheckWhetherLeadingSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is a
%% space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is not
%% a space-token>}%
%%
%% Due to \romannumeral0-expansion the result is delivered after two
%% expansion-steps/after two "hits" by \expandafter.
%%
\long\def\UDCheckWhetherLeadingSpace#1{%
\romannumeral0\UDCheckWhetherNull{#1}%
{\UDfirstoftwo\expandafter{} \UDsecondoftwo}%
{\expandafter\UDsecondoftwo\string{\UDInnerCheckWhetherLeadingSpace.#1 }{}}%
}%
\long\def\UDInnerCheckWhetherLeadingSpace#1 {%
\expandafter\UDCheckWhetherNull\expandafter{\UDsecondoftwo#1{}}%
{\UDExchange{\UDfirstoftwo}}{\UDExchange{\UDsecondoftwo}}%
{\UDExchange{ }{\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter}\expandafter\expandafter
\expandafter}\expandafter\UDsecondoftwo\expandafter{\string}%
}%
%%=============================================================================
%% Extract first inner undelimited argument:
%%=============================================================================
%% \UDExtractFirstArg{ABCDE} yields {A}
%% \UDExtractFirstArg{{AB}CDE} yields {AB}
%%
%% Due to \romannumeral0-expansion the result is delivered after two
%% expansion-steps/after two "hits" by \expandafter.
%%
\long\def\UDRemoveTillUDSelDOm#1#2\UDSelDOm{{#1}}%
\long\def\UDExtractFirstArg#1{%
\romannumeral0%
\UDExtractFirstArgLoop{#1\UDSelDOm}%
}%
\long\def\UDExtractFirstArgLoop#1{%
\expandafter\UDCheckWhetherNull\expandafter{\UDfirstoftwo{}#1}%
{ #1}%
{\expandafter\UDExtractFirstArgLoop\expandafter{\UDRemoveTillUDSelDOm#1}}%
}%
%%=============================================================================
%% Extract K-th inner undelimited argument:
%%=============================================================================
%% \UDExtractKthArg{<integer K>}{<list of undelimited args>}
%%
%% In case there is no K-th argument in <list of indelimited args> :
%% Does not deliver any token.
%% In case there is a K-th argument in <list of indelimited args> :
%% Does deliver that K-th argument with one level of braces removed.
%%
%% Examples:
%%
%% \UDExtractKthArg{0}{ABCDE} yields: <nothing>
%%
%% \UDExtractKthArg{3}{ABCDE} yields: C
%%
%% \UDExtractKthArg{3}{AB{CD}E} yields: CD
%%
%% \UDExtractKthArg{4}{{001}{002}{003}{004}{005}} yields: 004
%%
%% \UDExtractKthArg{6}{{001}{002}{003}} yields: <nothing>
%%
%% Due to \romannumeral0-expansion the result is delivered after two
%% expansion-steps/after two "hits" by \expandafter.
%%
\long\def\UDExtractKthArg#1{%
\romannumeral0%
% #1: <integer number K>
\expandafter\UDExtractKthArgCheck
\expandafter{\romannumeral\number\number#1 000}%
}%
\long\def\UDExtractKthArgCheck#1#2{%
\UDCheckWhetherNull{#1}{ }{%
\expandafter\UDExtractKthArgLoop\expandafter{\UDfirstoftwo{}#1}{#2}%
}%
}%
\long\def\UDExtractKthArgLoop#1#2{%
\expandafter\UDCheckWhetherNull\expandafter{\UDfirstoftwo#2{}.}{ }{%
\UDCheckWhetherNull{#1}{%
\expandafter\UDExchange
\romannumeral0\UDExtractFirstArgLoop{#2\UDSelDOm}{ }%
}{%
\expandafter\UDExchange\expandafter{\expandafter{\UDfirstoftwo{}#2}}%
{\expandafter\UDExtractKthArgLoop\expandafter{\UDfirstoftwo{}#1}}%
}%
}%
}%
%%=============================================================================
%% Fork whether argument either is an _explicit_
%% catcode 11(letter)-character-token of the set {s, i}
%% or is something else.
%%=============================================================================
%% \UDsiFork{<Argument to check>}{%
%% {<tokens to deliver in case <Argument to check> is s>}%
%% {<tokens to deliver in case <Argument to check> is i>}%
%% {<tokens to deliver in case <Argument to check> is empty or something else>}%
%% }%
%%
%% Due to \romannumeral0-expansion the result is delivered after two
%% expansion-steps/after two "hits" by \expandafter.
%%
\long\def\UDGobbleToExclam#1!{}%
\long\def\UDCheckWhetherNoExclam#1{%
\expandafter\UDCheckWhetherNull\expandafter{\UDGobbleToExclam#1!}%
}%
\long\def\UDsiSelect#1!!s!i!#2#3!!!!{#2}%
\long\def\UDsiFork#1#2{%
\romannumeral
\UDCheckWhetherNoExclam{#1}{%
\UDsiSelect
!#1!s1!i!{\expandafter\UDsecondoftwo\UDExtractKthArg{3}{#2}}% empty
!!#1!i!{\expandafter\UDsecondoftwo\UDExtractKthArg{1}{#2}}% s
!!s!#1!{\expandafter\UDsecondoftwo\UDExtractKthArg{2}{#2}}% i
!!s!i!{\expandafter\UDsecondoftwo\UDExtractKthArg{3}{#2}}% something else without !
!!!!%
}{\expandafter\UDsecondoftwo\UDExtractKthArg{3}{#2}}% something else with !
}%
%%=============================================================================
%% The main routine which calls the main loop:
%%=============================================================================
%% \zIterator{<Argument where s respectively s to be replaced by S respectively I>}
%%
%% Due to \romannumeral0-expansion the result is delivered after two
%% expansion-steps/after two "hits" by \expandafter.
%%
\long\def\zIterator{%
\romannumeral0\zIteratorTailRecursiveLoop{}%
}%
%%=============================================================================
%% The main loop:
%%=============================================================================
%% \zIteratorTailRecursiveLoop{<list of tokens where replacement
%% is already done>}%
%% {<remaining list of tokens where replacement of
%% s/i by S/I must still be performed>}%
%%
%% In case the <remaining list of tokens where replacement of s/i by S/I must
%% still be performed> is empty, you are done, thus deliver the <list of tokens
%% where replacement is already done>.
%% Otherwise:
%% Check if the <remaining list of tokens where replacement of s/i
%% by S/I must still be performed> has a leading space.
%% If so: Add a space-token to the <list of tokens where replacement is
%% already done>.
%% Remove the leading space token from the <remaining list of tokens
%% where replacement of s/i by S/I must still be performed>
%% Otherwise:
%% Check if the <remaining list of tokens where replacement of s/i
%% by S/I must still be performed> has a leading brace.
%% If so: Extract its first component/its first undelimited argument
%% and apply this routine to that extraction and add the
%% result (nested in braces) to the <list of tokens where
%% replacement is already done> .
%% Otherwise:
%% Check if the <remaining list of tokens where replacement
%% of s/i by S/I must still be performed>'s first component
%% is s or i.
%% If so: add "S" respectively "I" to the <list of tokens
%% where replacement is already done> .
%% Otherwise:
%% Add the <remaining list of tokens where replacement
%% of s/i by S/I must still be performed>'s first
%% component to the <list of tokens where replacement
%% is already done> .
%% Remove the first compoinent/the first undelimited argument from
%% the <remaining list of tokens where replacement of s/i by S/I
%% must still be performed>.
\long\def\zIteratorTailRecursiveLoop#1#2{%
% #1 - list of tokens where replacement is already done
% #2 - remaining list of tokens where replacement of s/i by S/I must
% still be performed
\UDCheckWhetherNull{#2}{ #1}{%
\UDCheckWhetherLeadingSpace{#2}{%
\expandafter\UDPassFirstBehindThirdToSecond\expandafter{%
\UDremovespace#2%
}{%
\UDPassFirstBehindThirdToSecond{#1 }{\UDsecondoftwo{}}%
}%
}{%
\expandafter\UDPassFirstBehindThirdToSecond\expandafter{%
\UDfirstoftwo{}#2%
}{%
\UDCheckWhetherBrace{#2}{%
\expandafter\UDPassFirstBehindThirdToSecond\expandafter{%
\romannumeral0\expandafter
\UDExchange\expandafter{\expandafter{%
\romannumeral0\expandafter\zIteratorTailRecursiveLoop
\expandafter{\expandafter}%
\romannumeral0\UDExtractFirstArgLoop{#2\UDSelDOm}%
}}{ #1}%
}{\UDsecondoftwo{}}%
}{%
\expandafter\UDsiFork
\romannumeral0\UDExtractFirstArgLoop{#2\UDSelDOm}{%
{\UDPassFirstBehindThirdToSecond{#1S}{\UDsecondoftwo{}}}%
{\UDPassFirstBehindThirdToSecond{#1I}{\UDsecondoftwo{}}}%
{%
\expandafter\UDPassFirstBehindThirdToSecond\expandafter{%
\romannumeral0\expandafter\UDExchange
\romannumeral0\UDExtractFirstArgLoop{#2\UDSelDOm}{ #1}%
}{\UDsecondoftwo{}}%
}%
}%
}%
}%
}%
{\zIteratorTailRecursiveLoop}%
}%
}%
%%=============================================================================
%% Usage-Examples of \zIterator which show that
%% - the result is delivered after two "hits" by \expandafter
%% - the mechanism is based on expansion only. No temporay assignments take
%% place. Therefore \zIterator can also be used in expansion-contexts,
%% e.g., within \csname..\endcsname,
%% - (unmatched) \if.. /\else/\fi in the argument do not disturb the mechanism.
%% - although the token \UDSelDOm is used as "sentinel-token" in some places,
%% that token can occur within the argument, thus there are no forbidden
%% tokens. (Except that you generally cannot use \outer tokens in
%% macro-arguments.)
%% - the mechanism can cope with things being nested in curly braces.
%%=============================================================================
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{%
\zIterator{A \TeX \is {\funny } {s sssi}i i \else \UDSelDOm {\fi } do ## not disturb me.}%
}
{\tt\meaning\test}%
\def\aSSbISSIIIISz{Yeah, that's it!}
\csname\zIterator{assbissiiiisz}\endcsname
\bye