How to get a function/macro definition from CL REPL?

common-lisp, lisp, sbcl, self-reference

Solution

Getting the source of a macro is not defined in Common Lisp.

This may work (Example from 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))))

An even more esoteric way is to alter the existing `DEFMACRO` to record its source. Many Lisp implementations have a non-standard feature called advising. LispWorks for example can advise macros:

CL-USER 31 > (defadvice (defmacro source-record-defmacro :after)
                 (&rest args)
               (setf (get (second (first args)) :macro-source) (first args)))
T

Above adds code to the standard `DEFMACRO` macro, which records the source on the symbol property list of the macro name. `defmacro` is the name of the thing to advise. `source-record-defmacro` is the chosen name of this advice. `:after` then specifies that the code should run after the normal `defmacro` code.

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)))

Again, this is completely non-standard - I'm not sure if a comparable mechanism exists for SBCL, though it has something called 'encapsulate'.

Problem

I've got another question involving self-reference in Common Lisp. I found a thread on Stack Exchange which poses a problem of writing the shortest program that would print all printable ASCII characters NOT present in the program's source code. This got me thinking how to tackle the problem in Common Lisp. I hit against two problems - one probably trivial, the other more tricky: First is the case of writing a CL script, e.g. starting with `#!/usr/bin/env sbcl --script`. I thought that through `*posix-argv*` I could access all command line arguments including the name of the called script. I also looked for the equivalent of Bash `$0` but could find none. What worked for me in the end is this ugly Bash-ified SBCL script, which explicitly passes `$0` to SBCL and proceeds from that: ``` #!/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))) ``` My question regarding this point is: can you think of any way of doing it without relying so heavily on Bash supplying relevant arguments? Or, more briefly: is there a CL (SBCL in particular) equivalent of `$0`? Now comes the part that I'm totally puzzled with. Before resorting to the script approach above I tried to accomplish this goal in a more REPL-oriented way. Based on the `&whole` specifier in `defmacro` and considerations in this thread I've tried to get the name of the macro from the `&whole` argument and somehow "read in" its source. And I have absolutely no idea how to do it. So in short: given the name of the macro, can I somehow obtain the `defmacro` form which defined it? And I'm talking about a generic solution, rather than parsing the REPL history. EDIT: Regarding mbratch's question about use of `macroexpand-1` here's how I do it: ``` (defmacro self-refer (&whole body) (macroexpand-1 `',body)) ``` With this call I'm able to obtain `(SELF-REFER)` by calling `(SELF-REFER)`. Which isn't much of a solution... I hope someone could point me in the right direction. Thanks!

Original source