如何在 TeX 中实现字符串缓冲区?

如何在 TeX 中实现字符串缓冲区?

我最近学习了如何创建一个宏来使用TeX 中的\afterassignmentand来迭代标记序列\let

目前,我正在尝试将其应用于标记化宏(其中“标记化”意味着围绕分隔字符/字符序列拆分字符序列);也就是说,stringTokenize执行以下操作的算法:

  1. character在字符串中迭代
  2. 如果character不是分隔符(例如,),则添加characterbuffer;如果character是分隔符,则展开buffer并对其执行某些操作(即传递给给定的宏)并清除buffer

然而这并不像我想象的那么简单,因为显然你不能将一个\let命令的字符扩展为另一个命令(为了创建一个“缓冲区”结构),例如,

\edef\buffer{\buffer\character}

将(在n迭代之后)扩展为

{\character\character(...)character}

n字符最后\let出现的次数\character

我该怎么做呢?


以下是我目前所掌握的信息:

\documentclass{minimal}

\makeatletter

\def\structures@stringIterate#1#2{%

    \newcount\@@index%

    \def\@@stop{§}
    \def\@@next{\afterassignment\@@each\let\@@current= }
    \def\@@each{%
      \ifx\@@current\@@stop%
         \let\@@next=\relax
      \else%
        \advance\@@index1\relax
         #2{\the\@@index}{\@@current}\let\@@next=\@@next
      \fi%
      \@@next % macro expansion must be delayed until the condition has been evaluated
    }

    \@@next#1\@@stop

}

\def\structures@stringTokenize#1#2#3{%

    \newcount\@@index
    \@@index 0\relax

    \def\@@buffer{}

    \def\@@test##1##2{%
        \ifx#2##2
            \advance\@@index 1\relax
            #3{\the\@@index}{\@@buffer}
            \def\@@buffer{} % reset the buffer
        \else
            \edef\@@buffer{\@@buffer##2} % gather character
        \fi
    }

    \structures@stringIterate{#1}{\@@test}

}

\makeatother

\begin{document}

\makeatletter

    \def\@@print#1#2{token (#1): ``#2''\par}
    \structures@stringTokenize{Humpty Dumpty sat on a wall, Humpty Dumpty had a great fall, All the King's horses and all the King's men, Couldn't put Humpty together again.}{,}{\@@print}

\makeatother

\end{document}

答案1

在 TeX 中抓取带有分隔符的 token 的常用方法如下

\catcode`\@=11 %

\def\grabargs#1#2#3{%
  \def\grabargs@aux##1#2{%
    \ifx\relax##1\relax%
    \else
      #3{##1}\par
      \expandafter\grabargs@aux
    \fi
  }%
  \grabargs@aux#1#2\relax#2
}
\def\print#1{`{\tt #1}'}
\catcode`\@=12 %

\grabargs
  {Humpty Dumpty sat on a wall, Humpty Dumpty had a great fall, All the King's horses and all the King's men, Couldn't put Humpty together again.}
  {,}
  {\print}

\bye

根据事情需要的复杂程度,我们可以使用更为奇特的方法来停止循环。

你不能在这里使用\let(至少不能以任何特别简单的方式):这会创建隐式标记,正如您所发现的,可以扩展,例如\edef。您可能希望做的是拾取空间标记:它们很难作为普通参数(#1)来抓取,因此您可以\futurelet在类似

\catcode`\@=11 %

\newcount\mycount
\def\grabargs#1#2#3{%
  \def\grabbed@tokens{}%
  \mycount\z@
  \def\grabargs@auxii{%
    \ifx\grab@token#2%
      \grabargs@auxiv
      \def\grabbed@tokens{}%
      \expandafter\grabargs@auxv
    \else
      \ifx\grab@token\@sptoken
        \expandafter\expandafter\expandafter\grabargs@space
      \else
        \ifx\grab@token\relax
        \else
          \expandafter\expandafter\expandafter\expandafter\expandafter
            \expandafter\expandafter\grabargs@auxiii
        \fi
      \fi
    \fi
  }%
  \def\grabargs@auxiv{%
    #3{\the\mycount}\grabbed@tokens
    \mycount\z@
  }%
  \grabargs@auxi#1#2\relax
}
\def\grabargs@auxi{%
  \futurelet\grab@token\grabargs@auxii
}
\def\grabargs@auxiii#1{%
  \edef\grabbed@tokens{%
    \grabbed@tokens
    #1%
  }%
  \advance\mycount\@ne
  \grabargs@auxi
}
\def\grabargs@auxv#1{\grabargs@auxi}
\expandafter\def\expandafter\grabargs@space\space{%
  \grabargs@auxiii\space
}
\def\:{\let\@sptoken= } \:  %
\def\print#1#2{Tokens: #1 `{\tt #2}'\par}
\catcode`\@=12 %

\grabargs
  {Humpty Dumpty sat on a wall, Humpty Dumpty had a great fall, All the King's horses and all the King's men, Couldn't put Humpty together again.}
  {,}
  {\print}

\bye

这里的想法\futurelet和你的想法非常相似\afterassignment\@@each\let\@@current=,只是没有从输入流中删除标记。我们使用抓取一个标记\futurelet然后可以检查它。如果它是标记,我们可以打印当前抓取的标记(和计数),然后循环。如果它是结束标记,我们停止,如果它是“正常”标记,我们可以抓取参数并存储。如果它是一个空格,我们会执行一个特殊版本的“grab”宏来处理空格。我在上面没有添加的是处理括号组的代码:可以做到这一点,但方法取决于你想对这些事情做什么。


请注意,在上述两种实现中,我们都存储了标记,这可能意味着类别代码 6 标记(通常#)存在问题,以及您希望如何管理换行符,ETC。这一切都可以解决,但如果没有更严格的规范,我就不管它了。另外,请注意,很容易陷入困境\futurelet\halign例如,参见在哪里可以找到有关 \futurelet 的恶劣行为的记录?

相关内容