假设我有一个像这样的宏
\def\myMacro#1{<some stuff>}
我这样称呼它
\myMacro{There are \some arguments \in \here g}
我如何迭代#1
参数中的每个单个标记\myMacro
?所以基本上我想知道如何迭代一个列表,该列表的分隔符不是逗号或任何其他字符,而是 TeX 应用的标记边界。请注意,参数可能包含未定义的控制序列,因此不能扩展它们。
我的意思举例:
\def\myMacro#1{<iterate over all tokens>|\string<current token>|}
\myMacro{A \test}
这将导致
|A|| ||\test|
需要注意的是,我也关心空格,所以它们不应该被吞掉。另外,我不想\mymacro
为了这个功能而执行 之外的任何代码(例如,在调用 之前更改空格的 catcode \myMacro
)。
由于我非常想了解这样的事情是如何工作的,如果您能解释一下您提供的代码是如何工作的以及为什么工作的,我将不胜感激:)
我的尝试是
\def\iterate#1{%
\tokenGrabber#1\relax<!;!>%
}
\def\tokenGrabber#1#2<!;!>{%
|\string#1|%
\noexpandarg%
\IfStrEq{#2}{\relax}{%
}{%
\tokenGrabber#2<!;!>%
}%
}
但这会吞噬空格,并且对于空输入或以空格结尾的输入会产生错误。
答案1
\documentclass{article}
\makeatletter
\def\endtest{\test!!!!}
\def\test#1{%
\par \bigskip\textbf{TESTING:} \texttt{\detokenize{#1}}\par
\testzz#1\endtest}
\def\testzz{\afterassignment\testzzz\let\tmp= }
\def\testzzz{%
\ifx\tmp\endtest
\else \texttt{\meaning\tmp}\par
\expandafter\testzz
\fi
}
\begin{document}
\test{123}
\test{There are \some arguments \in \here g}
\test{ a+ {x \sqrt{\frac}}}
\end{document}
请注意,此机制使用提供的列表\let
(带有且恰好一个空格的形式=
很重要,因此它不会在输入中删除空格或=),这使得检测空格和括号变得容易,但例如它仅有的捕获标记的含义,它无法区分,{
也\bgroup
无法区分任何未定义的命令,或者显示使用了哪个名称等,\zzzfoo
\undefined
它们都会在循环中以未定义的形式出现。
出于类似的原因,您无法在循环内重新构建任何与原始输入等同的东西。鉴于\frac{a}{b}
您得到的本质\frac\bgroup a\egrup\bgroup b\egroup
上是一般不可能重建工作分数。
所以……这些限制是否重要取决于循环的预期用途。
答案2
如果您需要它进行调试,它只有一行:
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\test}{m}
{
\tl_analysis_show:n { #1 }
}
\ExplSyntaxOff
\begin{document}
\test{123}
\test{There are \some arguments \in \here g}
\test{ a+ {x \sqrt{\frac}}}
\end{document}
如果你使用以下命令运行它pdflatex -interaction=nonstopmode
,控制台将显示
The token list contains the tokens:
> 1 (the character 1)
> 2 (the character 2)
> 3 (the character 3).
<recently read> }
l.13 \test{123}
The token list contains the tokens:
> T (the letter T)
> h (the letter h)
> e (the letter e)
> r (the letter r)
> e (the letter e)
> (blank space )
> a (the letter a)
> r (the letter r)
> e (the letter e)
> (blank space )
> \some (control sequence=undefined)
> a (the letter a)
> r (the letter r)
> g (the letter g)
> u (the letter u)
> m (the letter m)
> e (the letter e)
> n (the letter n)
> t (the letter t)
> s (the letter s)
> (blank space )
> \in (control sequence=\mathchar"3232=12850)
> \here (control sequence=undefined)
> g (the letter g).
<recently read> }
l.15 \test{There are \some arguments \in \here g}
The token list contains the tokens:
> (blank space )
> a (the letter a)
> + (the character +)
> (blank space )
> { (begin-group character {)
> x (the letter x)
> (blank space )
> \sqrt (control sequence=macro:->\protect \sqrt )
> { (begin-group character {)
> \frac (control sequence=macro:#1#2->{\begingroup #1\endgroup \over #2})
> } (end-group character })
> } (end-group character }).
<recently read> }
l.17 \test{ a+ {x \sqrt{\frac}}}
答案3
从中取出可扩展代码l3tl
并以经典风格重新编码,我们可能会这样做
\catcode`\@=11 %
\chardef\tl@exp@end=0 %
\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}
\long\def\@secondofthree#1#2#3{#2}
\long\def\@gobble#1{}
\long\def\tl@if@empty#1{%
\expandafter\ifx\expandafter\relax\detokenize{#1}\relax
\expandafter\@secondofthree
\fi
\@secondoftwo
}
\long\def\tl@if@head@N#1{%
\ifcat
\iffalse{\fi\tl@if@head@N@aux?#1 }%
\expandafter\@gobble\expandafter{\expandafter{\string#1?}}%
**%
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
\long\def\tl@if@head@N@aux#1 {%
\expandafter\tl@if@empty\expandafter{\@gobble#1}{^}{}%
\expandafter\@gobble\expandafter{\iffalse}\fi
}
\long\def\tl@if@head@group#1{%
\ifcat\expandafter\@gobble\expandafter{\expandafter{\string#1?}}**%
\expandafter\@secondoftwo
\else
\expandafter\@firstoftwo
\fi
}
\def\q@act@mark{\q@act@mark}
\def\q@act@stop{\q@act@stop}
\long\def\tl@act#1#2#3#4#5{%
\ifnum\iffalse{\fi`}=\z@\fi
\tl@act@loop#5\q@act@mark\q@act@stop
{#4}#1#2#3%
\tl@act@result{}%
}
\long\def\tl@act@loop#1\q@act@stop{%
\tl@if@head@N{#1}
{\tl@act@normal}
{%
\tl@if@head@group{#1}
{\tl@act@group}
{\tl@act@space}%
}%
#1\q@act@stop
}
\long\def\tl@act@normal#1#2\q@act@stop#3#4{%
\ifx\q@act@mark#1\expandafter\tl@act@end\fi
#4{#3}#1%
\tl@act@loop#2\q@act@stop
{#3}#4%
}
\long\def\tl@act@group#1#2\q@act@stop#3#4#5{%
#5{#3}{#1}%
\tl@act@loop#2\q@act@stop
{#3}#4#5%
}
\expandafter\long\expandafter\def\expandafter
\tl@act@space\space#1\q@act@stop#2#3#4#5{%
#5{#2}%
\tl@act@loop#1\q@act@stop
{#2}#3#4#5%
}
\long\def\tl@act@end#1\tl@act@result#2{%
\ifnum`{=\z@}\fi
\tl@exp@end
#2%
}
\long\def\iterate#1{%
\unexpanded\expandafter{%
\romannumeral\tl@act
\tl@iterate@normal
\tl@iterate@group
\tl@iterate@space
{ }
{#1}%
}%
}
\long\def\tl@iterate@normal#1#2{\tl@iterate@action{\string#2}}
\long\def\tl@iterate@group#1#2{\tl@iterate@action{{\detokenize{#2}}}}
\long\def\tl@iterate@space#1{\tl@iterate@action{ }}
\long\def\tl@iterate@action#1#2\tl@act@result#3{%
#2%
\tl@act@result{#3|#1|}%
}
\catcode`\@=12 %
\iterate{There are \some arguments \in \here g}
\bye
(这使用了 e-TeX,但可以避免。)
基本思想是获取标记列表并检查第一个标记,然后再进行分支和处理。我没有这样做过,但组内的递归是可行的。请注意,所有括号组都变成{ ... }
(可扩展代码中不可避免的)。