迭代 token

迭代 token

假设我有一个像这样的宏

\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

enter image description here

\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,但可以避免。)

基本思想是获取标记列表并检查第一个标记,然后再进行分支和处理。我没有这样做过,但组内的递归是可行的。请注意,所有括号组都变成{ ... }(可扩展代码中不可避免的)。

相关内容