我一直在尝试实现一个宏来读取并将其参数转换为不同的形式......但我在寻找或创建最基本的解析工具时遇到了很多麻烦。
举个例子:我找不到方法来制作 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 ntent
as #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