解析基础知识

解析基础知识

我正在尝试掌握一次解析一个字符的文本的技巧。以下是我迄今为止发现的。这是最好的吗?我真的不想做所有这些 \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

在此处输入图片描述

相关内容