我在Common Lisp中有另一个涉及自引用的问题。我找到了一个thread on Stack Exchange,这会产生编写最短程序的问题,该程序将打印程序源代码中不存在的所有可打印ASCII字符。这让我想到如何解决Common Lisp中的问题。我遇到了两个问题 - 一个可能是微不足道的,另一个更棘手:
#!/usr/bin/env sbcl --script
开始。我认为通过*posix-argv*
我可以访问所有命令行参数,包括被调用脚本的名称。我也寻找相当于Bash $0
,但没有找到。最终对我有用的是这个丑陋的Bash-ified SBCL脚本,它明确地将$0
传递给SBCL并从中继续:
#!/bin/bash
#|
sbcl --script $0 $0
exit
|#
(defun file-string (path)
(with-open-file (stream path)
(let ((data (make-string (file-length stream))))
(read-sequence data stream)
data)))
(let* ((printable (mapcar #'code-char (loop for i from #x20 to #x7e collect i)))
(absent (set-difference
printable
(coerce (file-string (cadr *posix-argv*)) 'list))))
(print (coerce absent 'string)))
关于这一点我的问题是:你能否想到任何方式这样做而不依赖于Bash提供相关论点?或者,更简单地说:是否有一个CL(特别是SBCL)相当于$0
?&whole
中的defmacro
说明符和this thread中的考虑因素,我试图从&whole
论证中获取宏的名称,并以某种方式“读入”它的来源。我完全不知道该怎么做。所以简而言之:给定宏的名称,我能以某种方式获得定义它的defmacro
形式吗?我正在讨论通用解决方案,而不是解析REPL历史。
编辑:关于mbratch关于使用macroexpand-1
的问题,我在这里是如何做到的:
(defmacro self-refer (&whole body)
(macroexpand-1 `',body))
通过这个电话,我可以通过调用(SELF-REFER)
获得(SELF-REFER)
。这不是一个解决方案......我希望有人能指出我正确的方向。谢谢!
在Common Lisp中没有定义获取宏的源代码。
这可能有用(来自LispWorks的例子):
CL-USER 10 > (defmacro foo (a b) `(* (+ ,a ,b) (+ ,a ,a)))
FOO
CL-USER 11 > (pprint (function-lambda-expression (macro-function 'foo)))
(LAMBDA
(DSPEC::%%MACROARG%% #:&ENVIRONMENT1106 &AUX (#:&WHOLE1107 DSPEC::%%MACROARG%%)
(#:\(A\ ...\)1108 (CDR #:&WHOLE1107))
(#:CHECK-LAMBDA-LIST-TOP-LEVEL1110
(DSPEC::CHECK-LAMBDA-LIST-TOP-LEVEL '(A B)
#:&WHOLE1107
#:\(A\ ...\)1108
2
2
'NIL
:MACRO))
(A (CAR (DSPEC::THE-CONS #:\(A\ ...\)1108)))
(#:\(B\)1109 (CDR (DSPEC::THE-CONS #:\(A\ ...\)1108)))
(B (CAR (DSPEC::THE-CONS #:\(B\)1109))))
(DECLARE (LAMBDA-LIST A B))
(BLOCK FOO `(* (+ ,A ,B) (+ ,A ,A))))
更为深奥的方式是改变现有的DEFMACRO
来记录它的来源。许多Lisp实现都有一个称为advising的非标准功能。例如,LispWorks可以建议宏:
CL-USER 31 > (defadvice (defmacro source-record-defmacro :after)
(&rest args)
(setf (get (second (first args)) :macro-source) (first args)))
T
上面将代码添加到标准DEFMACRO
宏,该宏在宏名称的符号属性列表中记录源。 defmacro
是值得建议的东西。 source-record-defmacro
是这个建议的选择名称。然后:after
指定代码应该在正常的defmacro
代码之后运行。
CL-USER 32 > (defmacro foo (a b) `(* (+ ,a ,b) (+ ,a ,a)))
FOO
CL-USER 33 > (pprint (get 'foo :macro-source))
(DEFMACRO FOO (A B) `(* (+ ,A ,B) (+ ,A ,A)))
同样,这完全是非标准的 - 我不确定SBCL是否存在类似的机制,尽管它有一种称为“封装”的东西。
对Rainer Joswig的LispWorks解决方案的一个非常迟来的跟进。我最近一直在使用Allegro CL并发现了fwrap
设施。从概念上讲,它与上面的defadvice
非常相似,而且更加冗长。以下是ACL 10.0中Rainer示例的重复:
(def-fwrapper source-record-defmacro (&rest args)
(setf (get (second (first args)) :macro-source) (first args))
(call-next-fwrapper))
定义了fwrapper
后,你需要明确地“付诸行动”:
(fwrap 'defmacro 'srd 'source-record-defmacro)
在此之后,就像在Rainer的例子中一样:
CL-USER> (defmacro foo (a b) `(* (+ ,a ,b) (+ ,a ,a)))
FOO
CL-USER> (pprint (get 'foo :macro-source))
(DEFMACRO FOO (A B) `(* (+ ,A ,B) (+ ,A ,A)))
; No value