考虑以下一组定义...
\documentclass[12pt]{article}
\def\foreach{%
\begingroup
\catcode`\^^M=12 \xforeach}
{\catcode`\^^M=12
\gdef\xforeach #1^^M{%
\first #1,,\endgroup}}
\def\first #1,{%
\if,#1,
\else Do something with `#1'.\par\expandafter\first
\fi}
\begin{document}
\foreach a,cde
\end{document}
如果您能向我解释上面定义的\expandafter
宏的含义(或功能),我会很高兴。\first
我的猜测是\expandafter
关闭了\if
-test,因此\first
被插入了。如果我错了,我也不会感到惊讶。
我将感谢有经验的 TeX 用户的详细描述。
答案1
您的猜测是正确的。\expandafter
总是跳过一个标记(在本例中为\first
)并扩展下一个标记(在本例中为\fi
)。扩展\fi
(或\else
,有点相似)结束当前条件并删除标记\fi
。
假设\expandafter
不存在:的第一次迭代\first
(我认为可以使用更好的名称)将是:
\if,a,
\else Do something with `a'.\par\first
\fi cde,,\endgroup
测试\if
结果为假,因此\else
将采用该分支。Do something with `a'.\par
将被排版,然后\first
再次展开。这一次,\first
将抓取(所有内容到下一个 ,
)\fi cde
作为参数,下一次迭代将是:
% V----V frozen \relax
\if,\relax\fi cde,
\else Do something with `\fi cde'.\par\first
\fi cde,,\endgroup
现在测试将在条件完成之前\if
看到标记,因此 TeX 会插入一个\fi
冻结\relax
并且测试\if,\relax
结果为 false,因此 TeX 会跳到下一个\fi
,也就是 之后的那个\relax
。 Nowcde,
会被(错误地)排版,然后 TeX 仍然在\else
的第一次迭代的分支中\first
,会看到另一个\else
并会抱怨:
! Extra \else.
\first #1,->\if ,#1, \else
Do something with `#1'.\par \first \fi
l.19 \foreach a,cde
也就是说,我会稍微改变一下你的代码:
- 我会在循环开始之前发出
\endgroup
,这样你就不必担心,例如,PGF 内部定义的常见问题\foreach
; - 我会把所有的
Do something with `#1'
外部条件,以便参数中的可能条件标记(,,,\if
等等)不会干扰循环的条件;并且\else
\fi
- 我会使用更安全的排空测试(见这里举几个例子);
\if,#1,
如果参数中有条件标记(如上所示),并且循环的项目包含逗号(例如),则很危险\foreach a,{,b},c
。更好的是,我会使用一个唯一的标记来测试循环的结束,这样就可以允许空项(它们不在您当前的代码中)。
也就是说,以下是更改后的代码:
\documentclass{article}
\makeatletter
\def\foreach{%
\begingroup
\catcode`\^^M=12 \xforeach}
{\catcode`\^^M=12
\gdef\xforeach #1^^M{%
\endgroup%
\first #1,,}}
\def\first #1,{%
\if\relax\detokenize{#1}\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
{\@gobble}%
{Do something with `#1'.\par}%
\first}
\makeatother
\begin{document}
\foreach a,cde
\end{document}
您还可以扩展它以将do something
代码内联:
\documentclass{article}
\makeatletter
\def\foreach{%
\begingroup
\catcode`\^^M=12 \xforeach}
{\catcode`\^^M=12
\long\gdef\xforeach #1^^M#2{%
\endgroup%
\def\marian@temp##1{#2}%
\first #1,,}}
\def\first #1,{%
\if\relax\detokenize{#1}\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
{\@gobble}%
{\marian@temp{#1}}%
\first}
\makeatother
\begin{document}
\foreach a,cde
{Do something with `#1'.\par}
\end{document}
或者expl3
使用\clist_map_inline:nn
:
\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\cs_new_protected:Npn \foreach
{
\group_begin:
\char_set_catcode_other:N \^^M
\__marian_foreach:wn
}
\group_begin:
\char_set_catcode_other:N \^^M
\cs_new_protected:Npn \__marian_foreach:wn #1 ^^M #2
{
\group_end:
\clist_map_inline:nn {#1} {#2}
}
\group_end:
\ExplSyntaxOff
\begin{document}
\foreach a,cde
{Do something with `#1'.\par}
\end{document}