Просмотр или извлечение текстовых определений, введенных в REPL верхнего уровня, идеально для Clozure Common Lisp (CCL)

Работая в REPL верхнего уровня, я иногда забываю, какие определения я ввел в работающую систему lisp.

Я использую Clozure CL, и он предоставляет возможность сохранить приложение в виде изображения, что я и делаю, и могу продолжить с того места, на котором остановился, но в этот момент становится невозможным просмотреть весь код, если только я не набрал и не сохранил код отдельно в файл xyz уже.

Есть ли способ получить/извлечь/просмотреть введенные определения, чтобы сохранить их как исходный файл?


person Capstone    schedule 01.12.2016    source источник


Ответы (4)


Следующее извлечет все определения функций, введенные в пакет:

(defun get-all-symbols (&optional package)
  (let ((lst nil)
        (package (find-package package)) )
    (do-all-symbols (s lst)
      (when (fboundp s)
        (unless (and package (not (eql (symbol-package s) package)))
          (push (cons s (function-lambda-expression s)) lst) )))))

Попробуйте что-то вроде:

(get-all-symbols *package*)
person Leo    schedule 01.12.2016
comment
После ввода определения и последующего запуска (get-all-symbols) возникает ошибка. Значение GET-ALL-SYMBOLS не имеет ожидаемого типа FUNCTION. Кажется, он должен работать для меня, но я не мог заставить его работать. - person Capstone; 01.12.2016
comment
Предлагается: `(defun get-all-symbols (& optional package) (let ((list nil) (package (find-package package)) (do-all-symbols (s list) (when (fboundp s) (unless ( и package (not (eql (symbol-package s) package))) (let ((def (function-lambda-expression (coerce s 'function)))) (when def (push (list* 'defun s def) list ))))))))` - person BRPocock; 02.12.2016

Common Lisp (в общем) не предоставляет какого-либо стандартного способа «восстановить» исходный код определения после того, как оно было скомпилировано. Обычно он находится в любом файле или буфере, с которым вы работаете.

(Как указано в ответе Лео, есть Function-Lambda-Expression, который может дать вам некоторые определения функций. Это не поможет, скажем, с константами или классами, и не всегда будет работать — как говорит CLHS. , «Любая реализация может правомерно возвращать nil в качестве лямбда-выражения любой функции». clhs.lisp.se/Body/f_fn_lam.htm — его решение, безусловно, полезно в наиболее распространенных случаях, но оно не такое «общее», как это.)

Вы можете использовать набор макросов-оболочек, которые сохраняют формы, которые вы им передаете, в глобальной хеш-таблице, а затем восстанавливают исходный код из нее. Написание этого звучало как интересная небольшая задача, поэтому ниже я попытаюсь сделать что-то подобное.

Мое решение для глупых оберток

Обратите внимание, что «исходные» формы, спрятанные таким образом, не сохранят макросы для чтения, комментарии и т.п., и, вероятно, будут задыхаться от некоторых вещей, таких как defmethod, довольно ужасным образом. Это потому, что я вслепую сохраняю определения с ключом определяющей формы — например, defun — и только второе слово. Он недостаточно умен, чтобы заметить, если вы перепривязываете функцию как макрос или общую функцию (все три конфликтующих определения будут сохранены), он не читает комбинации методов или списки лямбда-выражений для сохранения различных методов или чего-либо подобного. Есть много других вещей, которые вы можете сделать — например, (SetF (FDefinition 'FOO) …) — которые могут обойти эти и остаться незамеченными, так что это далеко не «защита от дурака». Осторожно, лектор.

Макросы здесь пытаются наследовать документацию и лямбда-списки от базовых форм, поэтому они должны работать довольно хорошо с большинством IDE. В Slime они преуспевают.

Один из способов работать с ними — напрямую вызывать их; например, в вашем REPL вы можете напрямую

 My-Package> (use-package :wrap-defining-form)
 My-Package> (defun$ my-fn (x) (+ x (sqrt x)))

В пакете Wrap-Defining-Form.Shadowing предусмотрен более опасный/интересный способ, в котором макросы затеняют реальные определения пакета Common-Lisp

 CL-User> (in-package :CL-USER$)
 CL-User$> (defun blah (n) (loop repeat n do (format t "~&Blah …")))

Когда вы будете готовы «сохранить» данные, запустите (dump-definitions).

Я написал и протестировал это на SBCL, но старался позаботиться о том, чтобы оно работало во многих/большинстве других реализаций. В частности, я использовал одну не-ANSI функцию: SB-Introspect:Function-Lambda-List. Функция здесь Wrap-Defining-Form::Find-Function-Lambda-List будет искать во всех пакетах версию этой функции для вашей реализации. Если он не может найти его, не все потеряно; но вы не получите подсказок от вашей IDE о лямбда-списке обернутой функции. (Кажется, что Clozure работает — своего рода — для функций, но не для макросов. Вероятно, это можно улучшить.)

CL-USER> (describe 'defun$)
WRAP-DEFINING-FORM:DEFUN$
  [symbol]

DEFUN$ names a macro:
  Lambda-list: (NAME LAMBDA-LIST &BODY BODY)
  Documentation:
    Wrap `DEFUN' and save the original form.

    DEFUN: Define a function at top level.
  Source file: /home/brpocock/Private/wrap-defining-form.lisp
; No value

Без Function-Lambda-List обертка выглядит так:

  Lambda-list: (&REST UNKNOWN-LAMBDA-LIST)

… что не очень полезно.


обертка-определение-form.lisp

РЕДАКТИРОВАТЬ: Отлажено в Clozure. Опубликовано также на https://github.com/brpocock/wrap-defining-forms .

;;;; Wrap--Defining-Forms
;;; -*- Lisp -*-

(defpackage wrap-defining-forms
  (:use :common-lisp)
  (:documentation "Wrap  defining forms so  that they (try to)  save the
  source code of the definition being passed.")
  (:export #:wrap-defining-form #:dump-definitions

           #:defclass$
           #:defconstant$
           #:defgeneric$
           #:define-compiler-macro$
           #:define-condition$
           #:define-method-combination$
           #:define-modify-macro$
           #:define-setf-expander$
           #:define-symbol-macro$
           #:defmacro$
           #:defmethod$
           #:defpackage$
           #:defparameter$
           #:defsetf$
           #:defstruct$
           #:deftype$
           #:defun$
           #:defvar$))

(defpackage wrap-defining-forms.shadowing
  (:documentation "Wrapped forms like DEFUN$  are exported here with the
  names   of   the    forms   that   they   wrap,    like   DEFUN,   for
  shadowing imports.")
  (:export #:defclass
           #:defconstant
           #:defgeneric
           #:define-compiler-macro
           #:define-condition
           #:define-method-combination
           #:define-modify-macro
           #:define-setf-expander
           #:define-symbol-macro
           #:defmacro
           #:defmethod
           #:defpackage
           #:defparameter
           #:defsetf
           #:defstruct
           #:deftype
           #:defun
           #:defvar)
  (:use))

;; Clozure appears  to be  “smart” and adds  Common-Lisp even  though we
;; didn't ask for it (and explicily don't want it)
#+ccl (unuse-package '(:ccl :common-lisp)
                     :wrap-defining-forms.shadowing)

(defpackage :common-lisp-user/save-defs
  (:nicknames :cl-user$)
  (:use :common-lisp :common-lisp-user)
  (:import-from :wrap-defining-forms #:dump-definitions)
  (:shadowing-import-from :wrap-defining-forms.shadowing
                          #:defclass
                          #:defconstant
                          #:defgeneric
                          #:define-compiler-macro
                          #:define-condition
                          #:define-method-combination
                          #:define-modify-macro
                          #:define-setf-expander
                          #:define-symbol-macro
                          #:defmacro
                          #:defmethod
                          #:defpackage
                          #:defparameter
                          #:defsetf
                          #:defstruct
                          #:deftype
                          #:defun
                          #:defvar))
;; Clone any other functions you may have packed into CL-User.
(with-package-iterator (next-symbol :common-lisp-user :internal)
  (loop for symbol = (next-symbol) 
        while symbol
        for sibling = (intern (symbol-name symbol) (find-package :cl-user$))
        when (and (fboundp symbol)
                  (not (fboundp sibling)))
          do (setf (fdefinition sibling) (fdefinition symbol))))
(in-package "WRAP-DEFINING-FORMS")

(defvar *definitions* (make-hash-table)
  "Copies   of    forms   defined    by   the   wrappers    created   by
  `WRAP-DEFINING-FORM' which can be stashed with `DUMP-DEFINITIONS'")

#+ccl
(defun ccl-mock-lambda-list (function)
  (if (macro-function function)
      (list '&rest 'macro-lambda-list)
      (multiple-value-bind (required optional restp
                            keywords) 
          (ccl:function-args (fdefinition function))
        (concatenate ' list
                       (loop repeat required 
                             collect (gensym "ARG-"))
                       (when (and optional (plusp optional))
                         (cons '&optional
                               (loop repeat optional
                                     collect (gensym "OPT-"))))
                       (when restp
                         (list '&rest 'rest))
                       (when (and keywords (plusp keywords))
                         (list '&key '&allow-other-keys))))))

(defun find-function-lambda-list ()
  "Find the implementation's version  of `FUNCTION-LAMBDA-LIST' if there
is  one.  That  way,  Slime  and  friends  can  still  give  the  proper
lambda-list  for the  wrapped form.  If it  can't be  found, this  will
return a stub with just a &rest-var."
  (or
   #+sbcl #'sb-introspect:function-lambda-list
   #+ccl #'ccl-mock-lambda-list
   #-(or ccl sbcl)
   (dolist (package (list-all-packages))
     (let ((sym (find-symbol "FUNCTION-LAMBDA-LIST" package)))
       (when (fboundp sym)
         (return-from find-function-lambda-list sym)))) 
   (lambda (function)
     (declare (ignore function))
     (list '&rest 'unknown-lambda-list))))

(defmacro wrap-defining-form (cl-form) 
  "Assuming  that CL-FORM  is a  symbol for  a macro  or function  which
defines something  interesting (eg, “Defun”),  this will create  a macro
with the same  name with a trailing  “$” that will save  the source tree
before passing on the form to CL-FORM.

EG:  (wrap-defining-form  defun)  provides  a  “defun$”  which  has  the
additional side effect of storing the source form in *DEFINITIONS*.

Definitions saved can be recovered by `DUMP-DEFINITIONS'.

This  is not  industrial-strength; in  particular, I  expect it  to cope
poorly with DEFMETHOD."
  (check-type cl-form symbol)
  (let ((wrapper (intern (concatenate 'string (symbol-name cl-form) "$")))
        (wrapper.shadow (intern (symbol-name cl-form) :wrap-defining-forms.shadowing))
        (wrapped-lambda-list (funcall (find-function-lambda-list) 'defun)))
    (setf (gethash cl-form *definitions*) (make-hash-table))
    `(prog1
         (defmacro ,wrapper (&whole whole ,@wrapped-lambda-list)
           (declare (ignore ,@(remove-if (lambda (form) (member form lambda-list-keywords))
                                         wrapped-lambda-list)))
           ,(concatenate 'string "Wrap `" (symbol-name cl-form) "' and save the original form." #(#\newline #\newline)
                         (symbol-name cl-form) ": " (or (documentation cl-form 'function)
                                                        "(see CLHS; no documentation here)"))
           (let ((defined (cons ',cl-form (cdr whole))))
             (setf (gethash (second whole) (gethash ',cl-form *definitions*))
                   defined)
             defined))
       (defmacro ,wrapper.shadow (&whole whole ,@wrapped-lambda-list)
         (declare (ignore ,@(remove-if (lambda (form) (member form lambda-list-keywords))
                                       wrapped-lambda-list)))
         ,(concatenate 'string "Wrap `COMMON-LISP:" (symbol-name cl-form) "' and save the original form."
                       #(#\newline #\newline)
                       (symbol-name cl-form) ": " (or (documentation cl-form 'function)
                                                      "(see CLHS; no documentation here)"))
         (let ((defined (cons ',cl-form (cdr whole))))
           (setf (gethash (second whole) (gethash ',cl-form *definitions*)) 
                 defined)
           defined)))))
(wrap-defining-form defclass)
(wrap-defining-form defconstant)
(wrap-defining-form defgeneric)
(wrap-defining-form define-compiler-macro)
(wrap-defining-form define-condition)
(wrap-defining-form define-method-combination)
(wrap-defining-form define-modify-macro)
(wrap-defining-form define-setf-expander)
(wrap-defining-form define-symbol-macro)
(wrap-defining-form defmacro)
(wrap-defining-form defmethod)
(wrap-defining-form defpackage)
(wrap-defining-form defparameter)
(wrap-defining-form defsetf)
(wrap-defining-form defstruct)
(wrap-defining-form deftype)
(wrap-defining-form defun)
(wrap-defining-form defvar)
(defun dump-definitions (&optional pathname)
  "Write  out   the  definitions  saved   by  `WRAP-DEFINING-FORM'-built
wrappers to PATHNAME (or *STANDARD-OUTPUT*)."
  (let (output
        (*print-case* :capitalize)
        ;; If writing to file, set margin at 79, but try to keep things under 72.
        (*print-right-margin* (if pathname 79 *print-right-margin*))
        (*print-miser-width* (if pathname 72 *print-miser-width*)))
    (unwind-protect
         (progn (setq output (if pathname
                                 (open pathname :direction :output
                                                :if-exists :rename
                                                :if-does-not-exist :create)
                                 *standard-output*))
                (multiple-value-bind  (sec min hr d m y) (decode-universal-time (get-universal-time))
                  (declare (ignore sec))
                  (format output
                          "~&~|~%;;; definitions as of ~d-~d-~d @ ~d:~2,'0d:
\(In-Package #:~a)
~{~{~2%~:<~W ~@_~:I~W ~:_~W~1I ~_~W~:>~}~^~|~}~%~|~%" ; from CLHS 22.2.2 SIMPLE-PPRINT-DEFUN
                          y m d hr min
                          (package-name *package*)
                          (remove-if #'null
                                     (loop for form being the hash-keys of *definitions*
                                           for defs = (gethash form *definitions*)
                                           collect (loop for definition being the hash-values of defs
                                                         collect definition))))))
      (when output (ignore-errors (close output))))))

Пример использования

CL-USER> (load "wrap-defining-form.lisp")

T
CL-USER> (use-package :wrap-defining-form)
T
CL-USER> (defun$ trash-word (word) 
           (let ((s (string word)))
             (sort (remove-if-not #'alpha-char-p s) #'char<)))
WARNING: redefining COMMON-LISP-USER::TRASH-WORD in DEFUN
TRASH-WORD
CL-USER> (trash-word 'Blatherscythe)
"ABCEEHHLRSTTY"
CL-USER> (describe 'trash-word)
COMMON-LISP-USER::TRASH-WORD
  [symbol]

TRASH-WORD names a compiled function:
  Lambda-list: (WORD)
  Derived type: (FUNCTION (T) (VALUES SEQUENCE &OPTIONAL))
  Source form:
    (SB-INT:NAMED-LAMBDA TRASH-WORD
        (WORD)
      (BLOCK TRASH-WORD
        (LET ((S (STRING WORD)))
          (SORT (REMOVE-IF-NOT #'ALPHA-CHAR-P S) #'CHAR<))))
; No value

CL-USER> (macroexpand-1 '(defun$ trash-word (word) 
           (let ((s (string word)))
             (sort (remove-if-not #'alpha-char-p s) #'char<))))
(DEFUN TRASH-WORD (WORD)
  (LET ((S (STRING WORD)))
    (SORT (REMOVE-IF-NOT #'ALPHA-CHAR-P S) #'CHAR<)))
T
CL-USER>  (dump-definitions)

;;; definitions as of 2016-12-1 @ 15:23:
(In-Package #:COMMON-LISP-USER)

(Defun Trash-Word (Word)
  (Let ((S (String Word)))
    (Sort (Remove-If-Not #'Alpha-Char-P S) #'Char<)))

NIL
CL-USER> (in-package :Common-Lisp-User/Save-Defs)
#<PACKAGE "COMMON-LISP-USER/SAVE-DEFS">
CL-USER$> (defun 2+ (n) (+ 2 n))
2+
CL-USER$> (describe '2+)
COMMON-LISP-USER/SAVE-DEFS::2+
  [symbol]

2+ names a compiled function:
  Lambda-list: (N)
  Derived type: (FUNCTION (T) (VALUES NUMBER &OPTIONAL))
  Source form:
    (SB-INT:NAMED-LAMBDA 2+
        (N)
      (BLOCK 2+ (+ 2 N)))
; No value
CL-USER$> (macroexpand-1 '(defun 2+ (n) (+ 2 n)))
(COMMON-LISP:DEFUN 2+ (N) (+ 2 N))
T
CL-USER$> (documentation 'defun 'function)
"Wrap `COMMON-LISP:DEFUN' and save the original form.

DEFUN: Define a function at top level."

CL-USER$> (dump-definitions)

;;; definitions as of 2016-12-1 @ 15:32:
(In-Package #:COMMON-LISP-USER/SAVE-DEFS)


(Common-Lisp:Defun 2+ (N) (+ 2 N))

(Common-Lisp:Defun Trash-Word (Word)
  (Let ((S (String Word)))
    (Sort (Remove-If-Not #'Alpha-Char-P S) #'Char<)))

    NIL

Резервное копирование файлов

Dump-Definitions и будет писать в файл. (Он устанавливает :If-Exists :Rename, так что вы также можете иметь одноуровневую защиту UNDO.)

     CL-USER$> (dump-definitions "saved.lisp")
     NIL
person BRPocock    schedule 01.12.2016
comment
Я еще немного взломал версию GitHub, см. - person BRPocock; 02.12.2016

Вот интерактивный сеанс с CCL:

? (declaim (optimize (debug 3)))
NIL

Вышеупомянутое здесь не является обязательным, но не помешает разрабатывать с высоким уровнем отладки.

? (defun foo (x) (+ 3 x))
FOO
? (inspect 'foo)
[0]     FOO
[1]     Type: SYMBOL
[2]     Class: #<BUILT-IN-CLASS SYMBOL>
        Function
[3]     INTERNAL in package: #<Package "COMMON-LISP-USER">
[4]     Print name: "FOO"
[5]     Value: #<Unbound>
[6]     Function: #<Compiled-function FOO #x3020004B3F7F>
[7]     Arglist: (X)
[8]     Plist: NIL
Inspect> 6
[0]     #<Compiled-function FOO #x3020004B3F7F>
[1]     Name: FOO
[2]     Arglist (analysis): (X)
[3]     Bits: 8388864
[4]     Plist: (CCL::PC-SOURCE-MAP #(17 70 15 22) CCL::FUNCTION-SYMBOL-MAP
        (#(X) . #(63 17 70)) CCL::%FUNCTION-SOURCE-NOTE ...)
[5]     Source Location: #<SOURCE-NOTE Interactive "(defun foo (x) (+ 3 x))">
Inspect 1> 5
[0]     #<SOURCE-NOTE Interactive "(defun foo (x) (+ 3 x))">
[1]     Type: SOURCE-NOTE
[2]     Class: #<STRUCTURE-CLASS SOURCE-NOTE>
[3]     SOURCE: #(40 100 101 102 117 ...)
[4]     FILENAME: NIL
[5]     FILE-RANGE: 23

Вы можете видеть, что даже из REPL и без запуска Slime, который также может хранить информацию о среде Emacs, вы можете получить доступ к исходному коду FOO. Это можно использовать, если вы знаете, какую функцию вы хотите восстановить. Для записи вашего интерактивного сеанса следуйте совет jkiiski о DRIBBLE.

person coredump    schedule 01.12.2016

Возможно, вы легко сможете реализовать что-то подобное самостоятельно:

(defun my-repl (&optional (file-path "cl-history.lisp"))
  "Saves commands to a file"
  (loop
    (with-open-file (stream file-path
                            :direction :output
                            :if-does-not-exist :create
                            :if-exists :append) 
      (print '>)
      (let ((input (read)))
        (format stream "~A~%" input)
        (print (eval input))))))

Чтобы выйти из внутреннего цикла, вы должны ввести (quit).

В качестве альтернативы вы можете использовать com.informatimago.common-lisp.interactive.interactive:repl.

person tsikov    schedule 01.12.2016
comment
Какой инновационный способ сделать это! Я пробовал это, однако вызов (my-repl temp.file) создает temp.file в указанном месте, но это всегда 0 байтов. Почему-то на него ничего не пишется. Я ввел кучу определений, и когда вы их просматриваете, в примечании к источнику указано (my-repl temp.file) как источник, но в temp.file ничего нет. - person Capstone; 01.12.2016
comment
Способ заимствован из Land of Lisp и Программирование искусственного интеллекта. Обе книги очень рекомендуются. :) Я исправил функцию и добавил имя файла в качестве необязательного аргумента, поэтому вам не нужно вводить его каждый раз (я полагаю, вы хотите повторно использовать файл). Однако я советую вам использовать emacs со SLIME :) После того, как вы настроите и установите его, вам никогда не понадобится использовать такие инструменты. В качестве альтернативы вы можете использовать LispWorks. Я не вникал в это, так как это довольно дорого для меня, но я слышал, что это действительно хорошо. - person tsikov; 01.12.2016