我正在尝试掌握一次解析一个字符的文本的技巧。以下是我迄今为止发现的。这是最好的吗?我真的不想做所有这些 \edefs,因为它们浪费内存。
\documentclass{article}
\tracingmacros=1
\newcommand{\scanA}[1]% #1 = text to scan
{\futurelet\token\scanB#1 % does nothing for me
\scanC#1 % splits off first token
\edef\next{#1\relax}%
\expandafter\scanD\next% refined version
\expandafter\scanD\next% okay, scaning underway
\expandafter\scanD\next
\expandafter\scanE\next{} % end
}
\def\scanB #1 {\noindent scanB:\par token \token \par\#1 #1 \par}
\def\scanC #1#2 {\noindent scanC:\par\#1 #1 \par\#2 #2 \par}
\def\scanD #1#2\relax{\noindent scanD:\par\#1 #1 \par\#2 #2 \par
\edef\next{#2\relax}}
\def\scanE #1#2\relax{\noindent scanE:\par\#1 #1 \par\#2 \meaning #2
\ifx#2\relax \relax\par The end.\par\fi}
\begin{document}
\scanA{test}
\end{document}
经验教训:(好吧,我应该已经知道其中的一些,但是我不知道。)
\documentclass{article}
\tracingmacros=1
\def\scanA#1{(#1)}% copies 1 token
\def\scanB#1 {(#1)}% copies 1 word
\newcommand{\scan}[1]{\scanC#1\END}% loop until \END
\def\scanC#1{\ifx#1\END\else(#1)\expandafter\scanC\fi}% expand \fi before \scanC
\newcommand{\scanwords}[1]{\let\between=\empty\scanD#1 \END}
\def\scanD#1 {\ifx#1\END\else\between
\let\between=\wordfill% insert \wordfill between words
\scanC#1\END% scan letters of word
\expandafter\scanD\fi% expand \fi before \scanD
}
\def\END{almost anything}
\def\wordfill{( )}% \def once, \let repeatedly
\begin{document}
\Huge
\scanA two words
\scanB two words
\scan{t e s t}% ignores spaces
\scanwords{two words}
\end{document}
所有答案都比我第一次尝试的答案要好,但我需要接受一个答案来“关闭”这个问题。因为我的目标是学习如何更好地扫描...
这是我一直在尝试的另一种变体。它将文本存储为标记列表。
\documentclass{article}
\usepackage{lipsum}
\def\END{\END}
\newtoks\mytoks
\def\parse{\futurelet\next\special}% some tokens are ignored
\def\special{% \space and \bgroup
\expandafter\ifx\space\next\relax \mytoks=\expandafter{\the\mytoks\space}\fi%
\ifx\bgroup\next\relax \expandafter\copygroup\else
\expandafter\normal\fi}
\def\copygroup#1{\mytoks=\expandafter{\the\mytoks{#1}}\parse}%
\def\normal#1{%
\ifx\END#1\relax
\the\mytoks% end of environment
\else
\mytoks=\expandafter{\the\mytoks#1}%
\expandafter\parse\fi}
\begin{document}
\parse
\begin{center}
enviroment test
\end{center}
\noindent\hbox{parse} test
\END
\end{document}
答案1
这里接受的 TeX 解决方案有几个问题。其中之一是每一步都要读取整个参数,而不是只读取一个 token。其次是接受代码中的递归循环会生成嵌套构造,\if...\fi
而这在 TeX 中非常有限。
因此,我在此展示由 TeX 基元声明的常见扫描器,不存在上述问题。允许扫描空格,但不允许扫描括号(为简单起见)。
\def\scan#1{\scanA#1\end}
\def\scanA{\futurelet\next\scanB}
\def\scanB{\expandafter\ifx\space\next \expandafter\scanC \else \expandafter\scanE \fi}
\def\scanC{\afterassignment\scanD \let\next= }
\def\scanD{\scanE{ }}
\def\scanE#1{\ifx\end#1\else
(#1)% <- The processing over one token is here
\expandafter \scanA \fi
}
\scan{abcdef ghijkl mno}
\bye
编辑:如果保持空间行为不变(即它们被忽略),那么代码就会简单得多:
\def\scan#1{\scanA#1\end}
\def\scanA#1{\ifx\end#1\else
(#1)% <- The processing over one token is here
\expandafter \scanA \fi
}
答案2
每次扫描一个标记至少需要区分扫描的标记是空格还是左括号。这是因为在这些情况下,您无法使用单参数宏删除扫描的标记。
首先,让我们看看\futurelet
做了什么;你的\futurelet\token\scanB
告诉 TeX 查看 后面跟着什么标记\scanB
,但不删除它,然后进行赋值\let\token=<scanned token>
,最后“查看” \scanB
,它应该根据 的值做出决定\token
。
为了终止扫描,你必须在最后放置一些特殊的标记;这个标记通常是一个“夸克”,比如
\def\quark{\quark}
这样\scanB
就可以做到\ifx\token\quark
,并且在这种情况下,停止递归。让我们把到目前为止所拥有的东西付诸实践:
\makeatletter
\def\scan@quark{\scan@quark}% if we find it in bad places, we'll know!
\newcommand\scan[1]{\futurelet\@let@token\scan@aux@i#1\scan@quark}
\def\scan@aux@i{%
\ifx\@let@token\scan@quark
\expandafter\@gobbletwo
\else
\expandafter\@firstofone
\fi
{\scan@aux@ii}%
}
宏\scan@aux@ii
现在应该继续进行其他测试。我\@gobbletwo
在“真”情况下使用了 so 来吞噬\scan@aux@ii
和\scan@quark
。
如果您只想在某个标记处拆分输入,更好的方法是使用分隔参数:您可以在网站上找到几个示例。使用起来expl3
非常简单,因为有内置函数可以完成这项工作。
假设你有一个输入,比如\word{abc^def^ghi}
你想用交替颜色打印。下面是一个实现:
\documentclass{article}
\usepackage{xparse,xcolor}
\ExplSyntaxOn
\NewDocumentCommand{\word}{m}
{
\kormylo_word:n { #1 }
}
\seq_new:N \l_kormylo_word_fragment_seq
\bool_new:N \l_kormylo_second_color_bool
\cs_new_protected:Npn \kormylo_word:n #1
{
\kormylo_change_color:
\seq_set_split:Nnn \l_kormylo_word_fragment_seq { ^ } { #1 }
\seq_use:Nn \l_kormylo_word_fragment_seq { \kormylo_change_color: }
}
\cs_new_protected:Npn \kormylo_change_color:
{
\bool_if:NTF \l_kormylo_second_color_bool
{ \color{second} \bool_set_false:N \l_kormylo_second_color_bool }
{ \color{first} \bool_set_true:N \l_kormylo_second_color_bool }
}
\ExplSyntaxOff
\colorlet{first}{black}
\colorlet{second}{red}
\begin{document}
\word{su^per^cal^i^frag^i^lis^tic^ex^pi^al^i^do^cious}
\end{document}
请注意,您可以在分隔符周围使用空格以便更好地输入,这些空格将被忽略。
宏可以扩展以允许输入中的空格:只需在空格处拆分并进行映射。
\documentclass{article}
\usepackage{xparse,xcolor}
\ExplSyntaxOn
\NewDocumentCommand{\words}{m}
{
\kormylo_words:n { #1 }
}
\seq_new:N \l_kormylo_word_seq
\seq_new:N \l_kormylo_word_fragment_seq
\bool_new:N \l_kormylo_second_color_bool
\cs_new_protected:Npn \kormylo_words:n #1
{
\seq_set_split:Nnn \l_kormylo_word_seq { ~ } { #1 }
\seq_map_inline:Nn \l_kormylo_word_seq
{
\kormylo_word:n { ##1 }
\c_space_tl
}
}
\cs_new_protected:Npn \kormylo_word:n #1
{
\kormylo_change_color:
\seq_set_split:Nnn \l_kormylo_word_fragment_seq { ^ } { #1 }
\seq_use:Nn \l_kormylo_word_fragment_seq { \kormylo_change_color: }
}
\cs_new_protected:Npn \kormylo_change_color:
{
\bool_if:NTF \l_kormylo_second_color_bool
{ \color{second} \bool_set_false:N \l_kormylo_second_color_bool }
{ \color{first} \bool_set_true:N \l_kormylo_second_color_bool }
}
\ExplSyntaxOff
\colorlet{first}{black}
\colorlet{second}{red}
\begin{document}
\words{su^per^cal^i^frag^i^lis^tic^ex^pi^al^i^do^cious syl^la^ble con^cate^na^tion}
\end{document}
答案3
对于第一个答案,我假设您想要扫描纯文本,不扫描任何组命令等。并且不带任何空格。
这是(主要的)TeX 解决方案。
\documentclass{minimal}
\begin{document}
\def\END{}
\def\ENDEND{}
\newcommand*\scan[1]{\scani #1\END\ENDEND}
\def\scani#1#2\ENDEND{%
\ifx\END#1%
\else%
(#1)%
\scani#2\ENDEND%
\fi
}
\scan{test}
\end{document}
这是 LaTeX3 版本。
\documentclass{minimal}
\usepackage{expl3}
\begin{document}
\ExplSyntaxOn
\newcommand*\scan[1]
{
\tl_map_inline:nn {#1} { (##1) }
}
\ExplSyntaxOff
\scan{test}
\end{document}
两者都将输出
(测试)
处理空格,这有点棘手。我这里有一个 TeX 解决方案(来自 Usenet 时代),但我自己并不理解它。
对于 LaTeX3,以下是可以处理空格的解决方案:LaTeX3:带空格的 tl_map
或者你使用我的版本
\documentclass{minimal}
\usepackage{expl3}
\begin{document}
\ExplSyntaxOn
\newcommand*\scan[1]
{
\__scanloop: #1 \q_recursion_stop
}
\cs_new:Nn \__scanloop:
{
\peek_meaning_remove:NTF \q_recursion_stop
{}
{
\peek_charcode_remove:NTF \c_space_token
{
(~)
\__scanloop:
}
% else
{
\__scanloop_aux:
}}
}
\cs_new:Npn \__scanloop_aux: #1
{
( #1 )
\__scanloop:
}
\ExplSyntaxOff
\scan{test with spaces}
\end{document}
这将输出
(t)(e)(s)(t)( )(w)(i)(t)(h)( )(s)(p)(a)(c)(e)(s)
答案4
现在,它最多可以扫描一个段落,但如果添加宏层,我就可以扫描多个段落。
\documentclass{article}
\usepackage{xcolor}
\def\mycolor{1}
\newcommand\myparse[1]{%
\myparsehelpA#1 \relax\relax}
\def\myparsehelpA#1 #2\relax{%
\myparsehelpB#1\relax\relax%
\ifx\relax#2\else\ \myparsehelpA#2\relax\fi
}
\def\myparsehelpB#1#2\relax{%
\ifcat^#1%
\if1\mycolor\def\mycolor{2}\color{red}%
\else\def\mycolor{1}\color{black}%
\fi%
\else#1\fi%
\ifx\relax#2\else
\myparsehelpB#2\relax%
\fi
}
\begin{document}
\myparse{Th^is^ is ^a te^st of the e^merge^ncy broad^cast sy^stem}
\end{document}
宏\myparse
可以在其参数中处理一些宏,但前提是它们本身不带参数。例如,\myparse{Th^is^ is ^a te^st of the e^merge^ncy broad^cast \itshape sy^stem; \bfseries th^is is \mdseries on^ly a test.}
yields