Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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!

like image 984
Wojciech Gac Avatar asked Jan 12 '23 07:01

Wojciech Gac


1 Answers

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'.

like image 102
Rainer Joswig Avatar answered Jan 31 '23 01:01

Rainer Joswig