我想将已经宏扩展的Emacs Lisp代码还原成未宏扩展的形式。
宏会生成任意表达式,其中可能包含递归的宏。您没有通用的方法来恢复转换,因为它不是基于模式的。
即使宏是基于模式的,它们仍然可能是无限的。
即使宏不是无限的,它们肯定会在从未匹配的模式扩展中包含错误。给定任意代码尝试解开,它可能会匹配一个看起来像代码的扩展,并尝试恢复到其模式。即使没有错误,您仍然可以滥用这一点。
即使您可以还原宏扩展,有些宏会扩展为相同的代码。一种方法是在所有还原扩展等于减去运算符的情况下发出警告并提供重启,以便如果重启不处理信号,则选择第一个扩展;否则发出错误并提供重启,以便如果重启不处理信号,则发生错误。或者您可以根据代码所在的软件包配置它以在某些条件下选择某些宏。
实际上,很少有情况需要还原扩展。它可能是一个有用的开发工具,可以建议宏,但我通常不会依赖它进行整个源代码的转换。
你想要实现的一种方法是通过控制模式匹配来实现。你可以最初手动创建模式,这些模式已经直接处理你关心的情况,比如你提到的情况:
将 (if (not <cond>) <expr>)
和 (if (not <cond>) (progn <&expr>))
翻译为 (unless <cond> <&expr>)
。你需要决定 null
是否等同于 not
。我个人不会混淆 nil
的布尔含义和空列表或其他内容(例如没有结果、未找到、空对象、指示符等)。但也许像 Emacs 中那样老的 Lisp 代码会互换使用它们。
将 (if <cond> <expr>)
和 (if <cond> (progn <&expr>))
翻译为 (when <cond> <&expr>)
。
如果您觉得需要改进代码,请包括只有一个条件的 cond
。并且要小心只有条件的 cond 子句。
你应该再多准备几十个模式,以查看在时间(CPU)和空间(内存)方面匹配更多模式时的模式匹配行为。
从fare-quasiquote的描述中可以知道,optima不支持回溯,而您可能需要它。
但是,您可以使用复杂内部模式上的递归进行optima回溯,如果没有匹配项,则返回控制值以保持从外部输入搜索匹配模式。
另一种方法是将模式视为状态机的描述,并处理每个新令牌以推进当前状态机,直到其中一个达到结尾,丢弃无法推进的状态机。此方法可能会消耗更多的内存,具体取决于模式的数量、相似性(如果许多模式具有相同的起始标记,则将生成许多状态机),模式的长度以及最后但并非最不重要的是输入(s表达式)的长度。
这种方法的优点是,您可以交互式地使用它来查看哪些模式匹配了最多的标记,并且您可以为模式分配权重而不仅仅是使用第一个匹配项。
缺点是,很可能需要花费一些精力来开发它。
编辑:我刚刚笨拙地描述了一种
trie或
radix tree。
一旦你得到了可用的东西,也许可以尝试自动获取模式。这真的很难,你可能需要将其限制在简单的反引号上,并接受这样一个事实:你无法为包含更复杂代码的任何内容进行概括。
我相信最困难的是代码遍历,在源代码上已经很难了,但在宏展开的代码上更加困难。也许如果你能进一步扩大整个图片以理解目标,也许有人可以建议除了操作宏扩展代码之外更好的方法。
然而,人们会认为这种事情,S表达式转换,正是Lisp所擅长的领域。而且defmacro
似乎在Lisp中和Emacs Lisp中都可用。
因此,肯定存在程序转换系统或术语重写系统,可以在此处进行适应。
从使用“defmacro”扩展代码到所有这些泛化的步骤是巨大的。大多数Lisp开发人员至少都知道卫生宏,至少在符号作为变量方面。
但是仍然存在符号作为运算符的卫生宏
1,代码行走,与包含宏的交互(通常使用“macrolet”),等等。这太过复杂了。
通用Lisp在词法环境中计算复合形式的运算符,可能每个人都会制作假定使用符号的全局宏或函数定义的宏。但实际情况可能并非如此:
(defmacro my-macro-1 ()
`1)
(defmacro my-macro-2 ()
`(my-function (my-macro-1)))
(defun my-function (n)
(* n 100))
(macrolet ((my-macro-1 ()
`2))
(flet ((my-function (n)
(* n 1000)))
(my-macro-2)))
那最后一行将扩展为
(my-function (my-macro-2))
,这将被递归地扩展为
(my-function 2)
。当评估时,它将产生2000。
为了正确的操作符卫生,你需要做这样的事情:
(defmacro my-macro-2 ()
(flet ((my-macro-1-global (form env)
(funcall (macro-function 'my-macro-1) form env))
(my-function-global (&rest args)
(apply 'my-function args)))
(let ((my-macro-1-symbol (gensym (symbol-name 'my-macro-1)))
(my-function-symbol (gensym (symbol-name 'my-function))))
(setf (macro-function my-macro-1-symbol) #'my-macro-1-global)
(setf (symbol-function my-function-symbol) #'my-function-global)
`(,my-function-symbol (,my-macro-1-symbol)))))
根据这个定义,例子将会得到100。
Common Lisp有一些限制来避免这种情况,但它仅说明当在全局或局部重新定义
common-lisp
包中的符号时,后果是未定义的。它不要求发出错误或警告。
(原文链接)