在 TeX 中实现 car 和 cdr

在 TeX 中实现 car 和 cdr

我一直在尝试实现一个宏来读取并将其参数转换为不同的形式......但我在寻找或创建最基本的解析工具时遇到了很多麻烦。

举个例子:我找不到方法来制作 car(返回列表的第一个元素)或 cdr(返回列表的其余部分)的安全版本。

我从中学到这个答案LaTeX 包括名为\@car和 的实现\@cdr,定义如下:

\def\@car#1#2\@nil{#1}
\def\@cdr#1#2\@nil{#2}

不过这两者都只能单独使用,不能嵌套使用。

例如,以下内容会产生错误:

\@car \@cdr content\@nil\@nil

\car如果和像在 Lisp 中一样工作,则正确的输出\cdr将是“o”:传递给的其余参数的第一个元素\cdr

但是,\@car首先运行,并且不执行其输入,而是将 视为\@cdr#1视为content#2然后返回\@cdr

这使得剩余的输入为\@cdr\@nil,从而导致错误“段落在\@cdr完成之前结束”。

如果我们尝试,我们也不能使用\@cdr多次来修剪第一个标记:

\@cdr \@cdr content\@nil\@nil

...结果是content\@nil,因为第一个\@cdr在运行之前丢弃了第二个。

我觉得答案应该是

\def\car#1{\@car #1\@nil}
\def\cdr#1{\@cdr #1\@nil}

...如果可以告诉 TeX 评估 #1 直到它不包含任何宏,然后再将其传递给\@car\@cdr

为此,我还尝试过:

\def\car#1{\edef\temp{#1}\expandafter\@car\temp\@nil}
\def\cdr#1{\edef\temp{#1}\expandafter\@cdr\temp\@nil}

\car{content}...这对于或再次正常工作\cdr{content},但导致文档编译悄然失败\car{\cdr{\cdr{content}}},而我不知道原因。

关于如何安全实施 car 和 cdr ,您有什么想法吗?

答案1

Werner 很好地解决了这个具体问题。更广泛地说,TeX 是一种宏扩展语言,其工作方式与使用函数的语言非常不同。在某些情况下,有一些技巧可以产生类似函数的行为(使用 可扩展\romannumeral,使用 不可扩展\edef),但这取决于确切的要求。在这里,取决于你想要什么,比如

\documentclass{article}
\makeatletter
\def\car#1{\expandafter\@car\romannumeral-`\Q#1\@nil}
\def\cdr#1{\expandafter\@cdr\romannumeral-`\Q#1\@nil}
\makeatother
\begin{document}
\car{\car{content}}

\cdr{\cdr{content}}

\cdr{\cdr{\cdr{content}}}
\end{document}

可能合适。这将完全展开内容,如果只是文本,则没问题,但在其他情况下可能不行。(注意:\romannumeral扩展以空格或其他不可扩展的标记结束,因此此技巧仍然取决于内容的确切性质。可以设置一个循环来尝试处理此问题:例如参见可扩展的令牌完整扩展,保留 catcodes如何扩展基于 \romannumeral 的 \fullyexpand 来处理空/全空间参数?。您没有说明对于非可扩展材料应该发生什么,这是该方法的另一个问题。)

答案2

以下产生您想要的结果,即o

\def\@car#1#2\@nil{#1}%
\def\@cdr#1#2\@nil{#2}%
\expandafter\@car \@cdr content\@nil\@nil

您遇到的问题与 LaTeX 吞噬其参数的方式有关,因此间接与事物的扩展方式有关。

仅使用

\@car \@cdr content\@nil\@nil

意味着\@car首先执行的是。根据其定义,它抓取三个标记作为参数(最后一个是“分隔符”),在本例中是\@cdr(第一个或#1),content(第二个或#2)和\@nil(第三个)。它返回到输入流#1,即\@cdr。因此,输入流中剩下的是\@cdr\@nil,根据\@cdr的定义,它抓取三个标记。第一个是\@nil。虽然第二个可能是任何东西,但在段落结束之前没有第三个(或“分隔符”)的匹配项,从而导致错误。

使用\tracingall,在 中输出以下内容.log

\@car #1#2\@nil ->#1
#1<-\@cdr 
#2<-content

\@cdr #1#2\@nil ->#2
#1<-\@nil 
#2<-... 

在执行中

\expandafter\@car \@cdr content\@nil\@nil

首先扩展(或执行)“内部函数”,留\@car ontent\@nil在输入流中,正确提取o(as #1,with ntentas #2)。

使用\tracingall,在 中输出以下内容.log

\@cdr #1#2\@nil ->#2
#1<-c
#2<-ontent

\@car #1#2\@nil ->#1
#1<-o
#2<-ntent

答案3

您可能会发现lambda-lists很有趣。还有一个TUGboat 文章关于包装,读起来都很有趣。

使用示例:

\catcode`@=11
\input lambda.sty
\let\car\Head
\let\cdr\Tail
\def\mylist{\Listize[a,b,c]}
\car{\cdr{\mylist}} % => b
\bye

相关内容