4

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!

Community
  • 1
  • 1
Wojciech Gac
  • 1,538
  • 1
  • 16
  • 30
  • 1
    For (1) you can use *posix-argv* for the arguments and *load-pathname* for the actual name of the called script. – lurker Jan 31 '14 at 11:19
  • Thanks, mbratch. That simplifies the thing a lot. – Wojciech Gac Jan 31 '14 at 11:44
  • For (2) have you looked at `macroexpand-1`? – lurker Jan 31 '14 at 13:28
  • Yes, I have, but unfortunately it does only part of what's needed. If I eval something like: ``(defmacro self-refer (&whole body) `(macroexpand-1 ',body))`` I get in response `(MACROEXPAND-1 '(SELF-REFER))`, which is the body of the `self-refer` macro, but not the entire definition. This hardly meets the requirement of program accessing its *entire* source. – Wojciech Gac Jan 31 '14 at 13:55
  • How are you wanting/trying to use `self-refer`? Perhaps a trial code sample in your second bullet would help clarify the problem statement. – lurker Jan 31 '14 at 14:12
  • Hey, mbratch. Just to make sure you notice, I edited the second bullet with my use example. – Wojciech Gac Jan 31 '14 at 15:35

2 Answers2

7

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

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • 1
    Rainer, your first example returns `nil` in SBCL and Clozure CL. As for the second, I haven't found much advice on using `encapsulate` in SBCL, but the CMUCL manual contains some information. I'll try to adapt your solution. Anyway, your response only convinces me, that LispWorks is much cooler than I thought. – Wojciech Gac Feb 01 '14 at 07:54
  • Thank you for the example on advising in LispWorks - what an amazing feature! – Ashok Khanna Jan 02 '22 at 19:53
2

A very belated followup to Rainer Joswig's LispWorks solution. I've been using Allegro CL lately and discovered the fwrap facility. Conceptually it's very similar to the defadvice above and slighly more verbose. Here's a re-iteration of Rainer's example in ACL 10.0:

(def-fwrapper source-record-defmacro (&rest args)
  (setf (get (second (first args)) :macro-source) (first args))
  (call-next-fwrapper))

Having defined an fwrapper you need to "put it into action" explicitly:

(fwrap 'defmacro 'srd 'source-record-defmacro)

After this it's like in Rainer's example:

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
Wojciech Gac
  • 1,538
  • 1
  • 16
  • 30