假设您有一个非分隔的宏参数,它形成一个字符串/任意长度的类别代码 12 的字符标记序列。
并且该字符串中某些字符的所有实例都应被其他标记序列替换。
例如, 的所有实例都应被控制字标记 替换, 的所有实例都应被 替换, 的所有实例都应被 替换, 的所有实例都应被控制字标记 替换, 的所有实例都应被 替换。A12
\foo
B12
\bar\bar
C12
\baz\baz\baz
D12
\foobar
E12
\foobaz\foobaz
实现此类字符替换的(可扩展)机制的有效方法是什么?
如果您希望字符及其替换不是硬编码而是通过宏参数提供,那么实现此类字符替换的(可扩展)机制的有效方法是什么?
在尾部递归循环中通过分隔参数“过滤”字符?在尾部递归循环中通过- / -比较级联
“过滤”字符? / ? \if
\ifx
\uppercase
\lowercase
答案1
基本上我使用的代码https://tex.stackexchange.com/a/521595/134574,但带有额外的 catcode-12 请求和调用宏时给出的输入到输出映射。假设输入标记列表是(扩展的)\detokenize{AxBxC{CxBxA}}{AxBxC}
(即前两个块是 catcode 12,最后一个是 catcode 11),替换是{A}{\foo} {B}{\bar} {C}{\baz}
,则输出将如下所示:
此方法将保留所有输入标记(空格、分组标记等),并且仅替换请求的标记(catcode-1 和 catcode-2 标记对被规范化为{
和除外}
)。如果您可以保证输入标记列表的任何内容(例如,它没有空格和分组标记),那么代码将成为很多更简单。
您可以像这样使用宏:
\parse{AxBxC{CxBxA}{AxBxC}}{%
{A}{\foo}
{B}{\bar}
{C}{\baz}
}
代码如下:
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewExpandableDocumentCommand \parse {+m+m}
{ \fan_parse:nn {#1} {#2} }
\cs_new:Npn \fan_parse:nn #1 #2
{ \exp_args:No \exp_not:o { \__fan_parse:nn {#1} {#2} } }
\cs_new:Npn \__fan_parse:nn #1 #2
{
\exp:w
\group_align_safe_begin:
\__fan_parse_loop:w #1
\q_recursion_tail \q_recursion_stop
\__fan_replacement:n {#2}
\__fan_result:n { }
}
\cs_new:Npn \__fan_end:w
\__fan_replacement:n #1
\__fan_result:n #2
{
\group_align_safe_end:
\exp_end:
#2
}
\cs_new:Npn \__fan_parse_loop:w #1 \q_recursion_stop
{
\tl_if_head_is_N_type:nTF {#1}
{ \__fan_N_type:N }
{
\tl_if_head_is_group:nTF {#1}
{ \__fan_group:nw }
{ \__fan_space:w }
}
#1 \q_recursion_stop
}
\cs_new:Npn \__fan_N_type:N #1
{
\quark_if_recursion_tail_stop_do:Nn #1
{ \__fan_end:w }
\__fan_parse_specials:Nw #1
}
\cs_new:Npn \__fan_group:nw #1 #2 \__fan_replacement:n #3
{
\exp_args:NNo \exp_args:No \__fan_group:n
{ \__fan_parse:nn {#1} {#3} }
#2 \__fan_replacement:n {#3}
}
\cs_new:Npn \__fan_group:n #1 { \__fan_add_result:nw { {#1} } }
\exp_last_unbraced:NNo
\cs_new:Npn \__fan_space:w \c_space_tl { \__fan_add_result:nw { ~ } }
\cs_new:Npn \__fan_add_result:nw #1 #2 \q_recursion_stop
\__fan_replacement:n #3
\__fan_result:n #4
{
\__fan_parse_loop:w #2 \q_recursion_stop
\__fan_replacement:n {#3}
\__fan_result:n {#4 #1}
}
% The macro that does the replacement
\cs_new:Npn \__fan_parse_specials:Nw #1 #2 \__fan_replacement:n #3
{
\exp_args:Nf \__fan_add_result:nw
{
\token_if_eq_catcode:NNTF \c_catcode_other_token #1
{
\exp_after:wN \exp_after:wN \exp_after:wN \exp_stop_f:
\str_case:nnF {#1} {#3}
{#1}
}
{#1}
}
#2 \__fan_replacement:n {#3}
}
\ExplSyntaxOff
\begin{document}
\edef\tmp{{\detokenize{AxBxC{CxBxA}}{AxBxC}}}
\ttfamily\expandafter\parse\tmp{%
{A}{\foo}
{B}{\bar}
{C}{\baz}
}
\end{document}
如果您想要替换所有内容(而不仅仅是 catcode-12 令牌),那么您需要删除以下 \token_if_eq_catcode:NNTF
内容\__fan_parse_specials:Nw
:
\cs_new:Npn \__fan_parse_specials:Nw #1 #2 \__fan_replacement:n #3
{
\exp_args:Nf \__fan_add_result:nw
{
\exp_after:wN \exp_after:wN \exp_after:wN \exp_stop_f:
\str_case:nnF {#1} {#3}
{#1}
}
#2 \__fan_replacement:n {#3}
}
答案2
没有问题tokcycle
。
\documentclass{article}
\usepackage{tokcycle}
\usepackage[T1]{fontenc}
\def\foo{Fxyz}
\def\bar{Bpdq}
\def\baz{Babc}
\def\foobar{FB123}
\def\foobaz{FB789}
\edef\theinput{\detokenize{XYZABCDEFG}}
\tokcycleenvironment\stringsub
{\expandafter\ifx\detokenize{A}##1\addcytoks{\foo}\else
\expandafter\ifx\detokenize{B}##1\addcytoks{\bar}\else
\expandafter\ifx\detokenize{C}##1\addcytoks{\baz}\else
\expandafter\ifx\detokenize{D}##1\addcytoks{\foobar}\else
\expandafter\ifx\detokenize{E}##1\addcytoks{\foobaz}\else
\addcytoks{##1}\fi\fi\fi\fi\fi}
{\processtoks{##1}}
{\addcytoks{##1}}
{\addcytoks{##1}}
\begin{document}
\theinput
\expandafter\stringsub\theinput\endstringsub
\detokenize\expandafter{\the\cytoks}
\end{document}
虽然 OP 没有询问输入流包含空格、组和/或宏的情况,但这也不会造成问题。例如:
\edef\theinput{\detokenize{XY} \noexpand\textit{\detokenize{ZABC}}\detokenize{DE}
\detokenize{FG}}
结果:
答案3
对我来说,这个问题似乎
- 成为一门学术学科。
- 有趣的。
请注意,应用\string
/ \meaning
/ \detokenize
(来自 ε-TeX 扩展)等并不一定只产生明确的类别代码 12(其他)字符标记:这些控制序列传递的空格(TeX 引擎内部字符编码中的字符代码 32)将始终属于类别代码 10(空格)。
我认为这个问题可以分为以下几个主题:
对给定标记的列表进行迭代,并在此通过仅适合纯扩展上下文的方法收集形成结果的标记。
在您的场景中,仅涉及 catcode-12 字符标记序列,花括号(类别代码 1 或 2 的显式字符标记)和空格(字符代码 32 和类别代码 10 的显式字符标记)等不需要考虑。
适用于纯扩展上下文的方法,用于确定某个标记是否是标记集合的元素,如果是,则确定它是哪一个。在您的场景中,标记集合将是、、、、,并且您需要确定字符串的元素是否也是该集合的元素,如果是,则确定是哪一个。
A12
B12
C12
D12
E12
如果可以将明确的 catcode-12-character-tokens 视为理所当然,则可以通过
\if
比较来进行“过滤”,以检查字符代码是否相同。如果不能理所当然地认为显式的 catcode-12 字符标记是可行的,而需要“可扩展性”,则“过滤”会变得更加复杂,因为
\if..
单独组合所有类型的比较可能不足以区分显式字符标记和隐式字符标记:例如,在传统的 TeX 中,无法通过这种方法来区分,仅两者都适用于纯扩展上下文,并且不基于 catcode-13-character-token 之间的分隔参数,通过该参数
\let
使其等于其 catcode-12-pendant 和 catcode-12-pendant。例如,
\catcode`\:=13 \expandafter\let\expandafter:\expandafter=\string:
仅通过方法可能很难区分两者都适用于纯扩展上下文并且不基于分隔参数。
:13
:12
如果结果
\escapechar
不肯定,您可能会面临更多问题:例如,
\escapechar=-1 \let\:=:
在传统的 TeX 中可能很难区分
:
和,\:
只能通过不基于分隔参数的“可扩展方法”来区分。
不管怎样。让我们坚持您提供的前提条件,并假设所讨论的字符串(可能由于一些复杂的预处理)仅包含类别代码 12 的显式字符标记。
如果要从字符串中生成实例的 catcode-12-character-token 集始终相同,则可以不让 TeX 应用任何内容\if
/ \ifx
-comparison,而是让 TeX 通过分隔参数进行“过滤”。
例如,只要确保控制符号标记\!
不作为参数的组成部分提供(就像一组 catcode-12-character-tokens 的情况一样),您就可以使用类似这样的方法进行过滤:
\begingroup
\catcode`\A=12 %
\catcode`\B=12 %
\catcode`\C=12 %
\catcode`\D=12 %
\catcode`\E=12 %
\def\firstofone#1{#1}%
\firstofone{%
\endgroup
\long\def\abcdeFork#1{%
\abcdeSelect
\!#1\!A\!B\!C\!D\!E\!{}% Case: #1 is empty
\!\!#1\!B\!C\!D\!E\!{\foo}% Case: #1 = A of catcode 12
\!\!A\!#1\!C\!D\!E\!{\bar\bar}% Case: #1 = B of catcode 12
\!\!A\!B\!#1\!D\!E\!{\baz\baz\baz}% Case: #1 = C of catcode 12
\!\!A\!B\!C\!#1\!E\!{\foobar}% Case: #1 = D of catcode 12
\!\!A\!B\!C\!D\!#1\!{\foobaz\foobaz}% Case: #1 = E of catcode 12
\!\!A\!B\!C\!D\!E\!{#1}% Case: #1 is something else.
\!\!\!\!%
}%
\long\def\abcdeSelect#1\!\!A\!B\!C\!D\!E\!#2#3\!\!\!\!{#2}%
}%
% Some dummy-definitions - be aware that \bar usually is already defined in TeX/LaTeX
% and the already existing definition gets overridden here:
\def\space{ }%
\def\foo{\string\foo}
\def\bar{\string\bar}
\def\baz{\string\baz}
\def\foobar{\string\foobar}
\def\foobaz{\string\foobaz}
\begingroup
\tt\frenchspacing
\string\abcdeFork\string{\string} yields: \abcdeFork{}
\string\expandafter\string\abcdeFork\string\expandafter\string{\string\string\space A\string} yields:
\expandafter\abcdeFork\expandafter{\string A}
\string\expandafter\string\abcdeFork\string\expandafter\string{\string\string\space B\string} yields:
\expandafter\abcdeFork\expandafter{\string B}
\string\expandafter\string\abcdeFork\string\expandafter\string{\string\string\space C\string} yields:
\expandafter\abcdeFork\expandafter{\string C}
\string\expandafter\string\abcdeFork\string\expandafter\string{\string\string\space D\string} yields:
\expandafter\abcdeFork\expandafter{\string D}
\string\expandafter\string\abcdeFork\string\expandafter\string{\string\string\space E\string} yields:
\expandafter\abcdeFork\expandafter{\string E}
\string\expandafter\string\abcdeFork\string\expandafter\string{\string\string\space X\string} yields:
\expandafter\abcdeFork\expandafter{\string X}
\endgroup
\bye
在不能确保\!
不作为参数的组成部分提供的情况下,您可以将其与检查结合起来\!
。
检查内容如下:
附加\!
到参数。然后让 TeX 将所有内容吞噬到 的第一个实例中\!
。如果结果是一个空参数,则没有\!
存在,否则至少有一个\!
存在,这表明该参数既不公正也不公正也不公正 也不公正也不公正。A12
B12
C12
D12
E12
您需要子程序来检查参数是否为空,并吞噬所有内容\!
。
在下面的例子中,检查参数是否为空是由宏完成的\CheckWhetherNull
。
\CheckWhetherNull
在我的回答中详细解释过“空标记列表的可扩展测试——方法、性能和稳健性”。在那个答案中它没有被命名\CheckWhetherNull
但它被命名了\CheckWhetherEmpty
。
\long\def\firstoftwo#1#2{#1}
\long\def\secondoftwo#1#2{#2}
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \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>
\long\def\CheckWhetherNull#1{%
\romannumeral0\expandafter\secondoftwo\string{\expandafter
\secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\secondoftwo\string}\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\firstoftwo\expandafter{} \secondoftwo}%
{\firstoftwo\expandafter{} \firstoftwo}%
}%
%%-----------------------------------------------------------------------------
\long\def\GobbleToExclam#1\!{}%
%%-----------------------------------------------------------------------------
%% Check whether argument does not contain \! (unless nested in braces and
%% thus not disturbing)
%%.............................................................................
%% \checkwhethernoexclam{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked does not contain \!>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked does contain \!>}%
%%
\long\def\checkwhethernoexclam#1{%
\expandafter\CheckWhetherNull\expandafter{\GobbleToExclam#1\!}%
}%
%
\begingroup
\catcode`\A=12 %
\catcode`\B=12 %
\catcode`\C=12 %
\catcode`\D=12 %
\catcode`\E=12 %
\def\firstofone#1{#1}%
\firstofone{%
\endgroup
\long\def\abcdeFork#1{%
\checkwhethernoexclam{#1}{%
\abcdeSelect
\!#1\!A\!B\!C\!D\!E\!{}% Case: #1 is empty
\!\!#1\!B\!C\!D\!E\!{\foo}% Case: #1 = A of catcode 12
\!\!A\!#1\!C\!D\!E\!{\bar\bar}% Case: #1 = B of catcode 12
\!\!A\!B\!#1\!D\!E\!{\baz\baz\baz}% Case: #1 = C of catcode 12
\!\!A\!B\!C\!#1\!E\!{\foobar}% Case: #1 = D of catcode 12
\!\!A\!B\!C\!D\!#1\!{\foobaz\foobaz}% Case: #1 = E of catcode 12
\!\!A\!B\!C\!D\!E\!{#1}% Case: #1 is something else without \!
\!\!\!\!%
}{#1}% Case: #1 is something else with \!
}%
\long\def\abcdeSelect#1\!\!A\!B\!C\!D\!E\!#2#3\!\!\!\!{#2}%
}%
% Some dummy-definitions - be aware that \bar usually is already defined in TeX/LaTeX
% and the already existing definition gets overridden here:
\def\space{ }%
\def\foo{\string\foo}
\def\bar{\string\bar}
\def\baz{\string\baz}
\def\foobar{\string\foobar}
\def\foobaz{\string\foobaz}
\def\!{\string\!}
\begingroup
\tt\frenchspacing
\string\abcdeFork\string{\string} yields: \abcdeFork{}
\string\expandafter\string\abcdeFork\string\expandafter\string{\string\string\space A\string} yields:
\expandafter\abcdeFork\expandafter{\string A}
\string\expandafter\string\abcdeFork\string\expandafter\string{\string\string\space B\string} yields:
\expandafter\abcdeFork\expandafter{\string B}
\string\expandafter\string\abcdeFork\string\expandafter\string{\string\string\space C\string} yields:
\expandafter\abcdeFork\expandafter{\string C}
\string\expandafter\string\abcdeFork\string\expandafter\string{\string\string\space D\string} yields:
\expandafter\abcdeFork\expandafter{\string D}
\string\expandafter\string\abcdeFork\string\expandafter\string{\string\string\space E\string} yields:
\expandafter\abcdeFork\expandafter{\string E}
\string\expandafter\string\abcdeFork\string\expandafter\string{\string\string\space X\string} yields:
\expandafter\abcdeFork\expandafter{\string X}
\string\abcdeFork\string{\string\!\string} yields: \abcdeFork{\!}
\endgroup
\bye
使用这种分叉/过滤技术,您可以创建一个尾部递归循环,用于迭代 catcode-12-character-tokens 列表并将每个字符传递给\abcdeFork
。
由于您给出了字符串中只能出现(显式?)catcode-12 字符标记的前提条件,因此可以将字符串作为显式(catcode-12-)字符标记的列表,并且循环不需要考虑类别代码 1 或 2 的空格标记和花括号/显式字符标记,并且可以按如下方式构建:
有一个尾递归宏来处理两个参数:
- 剩余的 catcode-12-character-tokens 列表。
- 构成迄今为止收集的结果的令牌。
如果剩余的 catcode-12-character-tokens 列表为空,则通过提供迄今为止收集的结果的标记来终止循环。
如果剩余的 catcode-12-character-tokens 列表不为空,则再次调用尾部递归宏,并使用修改后的参数,如下所示:
- 第一个元素从剩余的 catcode-12-character-tokens 列表中删除。
- 从剩余的 catcode-12-character-tokens 列表中提取第一个元素并通过处理它的结果
\abcdeFork
附加到形成迄今为止收集的结果的标记上。
它可能看起来像这样:
\long\def\firstoftwo#1#2{#1}
\long\def\secondoftwo#1#2{#2}
\long\def\PassFirstToSecond#1#2{#2{#1}}
\long\def\exchange#1#2{#2#1}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \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>
\long\def\CheckWhetherNull#1{%
\romannumeral0\expandafter\secondoftwo\string{\expandafter
\secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\secondoftwo\string}\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\firstoftwo\expandafter{} \secondoftwo}%
{\firstoftwo\expandafter{} \firstoftwo}%
}%
%%-----------------------------------------------------------------------------
\long\def\GobbleToExclam#1\!{}%
%%-----------------------------------------------------------------------------
%% Check whether argument does not contain \! (unless nested in braces and
%% thus not disturbing)
%%.............................................................................
%% \checkwhethernoexclam{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked does not contain \!>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked does contain \!>}%
%%
\long\def\checkwhethernoexclam#1{%
\expandafter\CheckWhetherNull\expandafter{\GobbleToExclam#1\!}%
}%
%
\begingroup
\catcode`\A=12 %
\catcode`\B=12 %
\catcode`\C=12 %
\catcode`\D=12 %
\catcode`\E=12 %
\def\firstofone#1{#1}%
\firstofone{%
\endgroup
\long\def\abcdeFork#1{%
\romannumeral0\checkwhethernoexclam{ #1}{%
\abcdeSelect
\!#1\!A\!B\!C\!D\!E\!{ }% Case: #1 is empty
\!\!#1\!B\!C\!D\!E\!{ \foo}% Case: #1 = A of catcode 12
\!\!A\!#1\!C\!D\!E\!{ \bar\bar}% Case: #1 = B of catcode 12
\!\!A\!B\!#1\!D\!E\!{ \baz\baz\baz}% Case: #1 = C of catcode 12
\!\!A\!B\!C\!#1\!E\!{ \foobar}% Case: #1 = D of catcode 12
\!\!A\!B\!C\!D\!#1\!{ \foobaz\foobaz}% Case: #1 = E of catcode 12
\!\!A\!B\!C\!D\!E\!{ #1}% Case: #1 is something else without \!
\!\!\!\!%
}{ #1}% Case: #1 is something else with \!
}%
\long\def\abcdeSelect#1\!\!A\!B\!C\!D\!E\!#2#3\!\!\!\!{#2}%
}%
%%-----------------------------------------------------------------------------
%% Extract first non-delimited argument from list:
%%
%% \romannumeral\ExtractFirstArgLoop{<List of non-delimited arguments>\SelDOm}
%% yields first element of <List of non-delimited arguments>.
%%
%% <List of non-delimited arguments> must not be empty.
%% <List of non-delimited arguments> may contain the token \SelDOm.
%%
%% \romannumeral\ExtractFirstArgLoop{uvwxy\SelDOm} yields {u}
%% \romannumeral\ExtractFirstArgLoop{{uv}wxy\SelDOm} yields {uv}
%%-----------------------------------------------------------------------------
\long\def\RemoveTillSelDOm#1#2\SelDOm{{#1}}%
\long\def\ExtractFirstArgLoop#1{%
\expandafter\CheckWhetherNull\expandafter{\firstoftwo{}#1}%
{0 #1}%
{\expandafter\ExtractFirstArgLoop\expandafter{\RemoveTillSelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%% The replacement-routine:
%%-----------------------------------------------------------------------------
\long\def\abcdeReplace#1{\romannumeral0\abcdeReplaceloop{}{#1}}%
\long\def\abcdeReplaceloop#1#2{%
% #1 = tokens forming the result collected so far
% #2 = list of remaining catcode-12-character-tokens
\CheckWhetherNull{#2}{ #1}{%
\expandafter\PassFirstToSecond\expandafter{\firstoftwo{}#2}{%
\expandafter\abcdeReplaceloop\expandafter{%
\romannumeral0%
\expandafter\exchange\expandafter{%
\romannumeral0\exchange{ }{%
\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter
}%
\expandafter\abcdeFork\romannumeral\ExtractFirstArgLoop{#2\SelDOm}%
}{ #1}%
}%
}%
}%
}%
% Some dummy-definitions - be aware that \bar usually is already defined in TeX/LaTeX
% and the already existing definition gets overridden here:
\def\space{ }%
\def\foo{\string\foo}
\def\bar{\string\bar}
\def\baz{\string\baz}
\def\foobar{\string\foobar}
\def\foobaz{\string\foobaz}
%
% Now let's define a macro holding a test-string:
\edef\teststring{%
\string s\string o\string m\string e\string t\string h%
\string i\string n\string g%
\string 1\string A\string 2\string E\string 3\string E%
\string 4\string B\string 5\string D\string 6\string D%
\string 7\string C\string 8\string C\string 9\string A%
\string s\string o\string m\string e\string t\string h%
\string i\string n\string g%
}
\begingroup
\font\myfont=cmtt8 at 7pt
\myfont
\frenchspacing
\string\teststring\space yields:
\teststring
\string\expandafter\string\abcdeReplace\string\expandafter\string{\string\teststring\string}
yields:
\expandafter\abcdeReplace\expandafter{\teststring}
\endgroup
\bye
的情况下
- 具有可扩展的替换机制和
- 一组 catcode-12 字符标记,其实例要从字符串中生成,它们并不总是相同的,而是可以通过宏参数“动态指定”,这些宏参数表示字符应如何被标记序列替换,
事情可能最终会以嵌套尾部递归循环结束,因为需要对形成字符串的 catcode-12-character-tokens 列表和替换指令列表进行迭代。
例如,嵌套尾部递归循环,其中显式类别代码 12 个字符标记“通过\if
比较进行过滤”是可行的,因为在这种情况下\if
,比较用于检查字符代码是否相同似乎足够了。
\Replace
以下示例中例程的语法为:
\Replace{%
⟨Catcode-12-Character-token-string where characters are to be replaced⟩
}%%
{{⟨Character 1⟩}{⟨Replacement of Character 1⟩}}%
{{⟨Character 2⟩}{⟨Replacement of Character 2⟩}}%
...
{{⟨Character K⟩}{⟨Replacement of Character K⟩}}%
}%
\long\def\firstoftwo#1#2{#1}
\long\def\secondoftwo#1#2{#2}
\long\def\PassFirstToSecond#1#2{#2{#1}}
\long\def\exchange#1#2{#2#1}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \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>
\long\def\CheckWhetherNull#1{%
\romannumeral0\expandafter\secondoftwo\string{\expandafter
\secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\secondoftwo\string}\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\firstoftwo\expandafter{} \secondoftwo}%
{\firstoftwo\expandafter{} \firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Extract first non-delimited argument from list:
%%
%% \romannumeral\ExtractFirstArgLoop{<List of non-delimited arguments>\SelDOm}
%% yields first element of <List of non-delimited arguments>.
%%
%% <List of non-delimited arguments> must not be empty.
%% <List of non-delimited arguments> may contain the token \SelDOm.
%%
%% \romannumeral\ExtractFirstArgLoop{uvwxy\SelDOm} yields {u}
%% \romannumeral\ExtractFirstArgLoop{{uv}wxy\SelDOm} yields {uv}
%%-----------------------------------------------------------------------------
\long\def\RemoveTillSelDOm#1#2\SelDOm{{#1}}%
\long\def\ExtractFirstArgLoop#1{%
\expandafter\CheckWhetherNull\expandafter{\firstoftwo{}#1}%
{0 #1}%
{\expandafter\ExtractFirstArgLoop\expandafter{\RemoveTillSelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%% The replacement-routine:
%%-----------------------------------------------------------------------------
\long\def\Replace{\romannumeral0\Replaceloop{}}%
\long\def\Replaceloop#1#2#3{%
% #1 = tokens forming the result collected so far
% #2 = list of remaining catcode-12-character-tokens
% #3 = list of replacements
\CheckWhetherNull{#2}{ #1}{%
\PassFirstToSecond{#3}{%
\expandafter\PassFirstToSecond\expandafter{\firstoftwo{}#2}{%
\expandafter\Replaceloop\expandafter{%
\romannumeral0%
\expandafter\exchange\expandafter{%
\romannumeral0%
\expandafter\InnerReplaceloop\romannumeral\ExtractFirstArgLoop{#2\SelDOm}{#3}%
}{ #1}%
}%
}%
}%
}%
}%
\long\def\InnerReplaceloop#1#2{%
% #1 = catcode-12-token
% #2 = remaining list of replacements
\expandafter\CheckWhetherNull\expandafter{\firstoftwo#2{}.}{ #1}{%
\if
\expandafter\expandafter
\expandafter \firstoftwo
\expandafter\firstoftwo
\romannumeral\ExtractFirstArgLoop{#2\SelDOm}{}#1%
\expandafter\firstoftwo
\else
\expandafter\secondoftwo
\fi
{%
\exchange{ }{%
\expandafter\expandafter
\expandafter \expandafter
\expandafter\expandafter
\expandafter
}%
\expandafter\expandafter
\expandafter \secondoftwo
\expandafter\firstoftwo
\romannumeral\ExtractFirstArgLoop{#2\SelDOm}{}%
}{%
\expandafter\PassFirstToSecond\expandafter{%
\firstoftwo{}#2%
}{\InnerReplaceloop{#1}}%
}%
}%
}%
%-------------------------------------------------------------------------------------
% Some dummy-definitions - be aware that \bar usually is already defined in TeX/LaTeX
% and the already existing definition gets overridden here:
\def\space{ }%
\def\foo{\string\foo}
\def\bar{\string\bar}
\def\baz{\string\baz}
\def\foobar{\string\foobar}
\def\foobaz{\string\foobaz}
%
% Now let's define a macro holding a test-string:
\edef\teststring{%
\string s\string o\string m\string e\string t\string h%
\string i\string n\string g%
\string 1\string A\string 2\string E\string 3\string E%
\string 4\string B\string 5\string D\string 6\string D%
\string 7\string C\string 8\string C\string 9\string A%
\string s\string o\string m\string e\string t\string h%
\string i\string n\string g%
}%
\begingroup
\font\myfont=cmtt8 at 7pt
\myfont
\baselineskip=8.2pt
\parindent=0pt
\frenchspacing
\string\teststring\space yields:
\medskip
\teststring
\medskip
\string\expandafter\string\Replace\string\expandafter\string{\string\teststring\string}\string{\hfil\break
\null\space\space\string{\string{A\string}\string{\string\foo\string}\string}\hfil\break
\null\space\space\string{\string{B\string}\string{\string\bar\string\bar\string}\string}\hfil\break
\null\space\space\string{\string{C\string}\string{\string\baz\string\baz\string\baz\string}\string}\hfil\break
\null\space\space\string{\string{D\string}\string{\string\foobar\string}\string}\hfil\break
\null\space\space\string{\string{E\string}\string{\string\foobaz\string\foobaz\string}\string}\hfil\break
\string}\hfil\break
yields:
\medskip
\expandafter\Replace\expandafter{\teststring}{%
{{A}{\foo}}%
{{B}{\bar\bar}}%
{{C}{\baz\baz\baz}}%
{{D}{\foobar}}%
{{E}{\foobaz\foobaz}}%
}
\endgroup
\bye