如何从CL REPL获取函数/宏定义?

时间:2014-01-31 10:08:32

标签: lisp common-lisp sbcl self-reference

我在Common Lisp中有另一个涉及自引用的问题。我找到了一个thread on Stack Exchange,这会产生编写最短程序的问题,该程序将打印程序源代码中不存在的所有可打印ASCII字符。这让我想到如何解决Common Lisp中的问题。我遇到了两个问题 - 一个可能是微不足道的,另一个更棘手:

  • 首先是编写CL脚本的情况,例如:以#!/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

  • 现在出现了我完全不解的部分。在采用上面的脚本方法之前,我尝试以更加面向REPL的方式实现此目标。基于&whole中的defmacro说明符和this thread中的注意事项,我试图从&whole参数中获取宏的名称,并以某种方式“读入”其来源。我完全不知道该怎么做。所以简而言之:给定宏的名称,我能以某种方式获得定义它的defmacro形式吗?我正在讨论通用解决方案,而不是解析REPL历史。

    编辑:关于mbratch关于使用macroexpand-1的问题,请问我这样做:

    (defmacro self-refer (&whole body)
      (macroexpand-1 `',body))
    

    通过此次通话,我可以通过拨打(SELF-REFER)获取(SELF-REFER)。这不是一个很好的解决方案...

我希望有人能指出我正确的方向。谢谢!

2 个答案:

答案 0 :(得分:5)

在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是否存在类似的机制,尽管它有一种称为“封装”的东西。

答案 1 :(得分:2)

对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