SIMPLE-READER-ERROR, недопустимый острый макро-символ, Subcharacter # \ <не определено для отправки char # \ # - PullRequest
1 голос
/ 01 апреля 2020

В главе 4 книги «Успешный Лисп» Дэвида Б. Ламкинса есть простое приложение для отслеживания банковских чеков.

https://dept-info.labri.fr/~strandh/Teaching/MTP/Common/David-Lamkins/chapter04.html

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

Макрос для сохранения и восстановления функций:

(defmacro def-i/o (writer-name reader-name (&rest vars))
  (let ((file-name (gensym))
        (var (gensym))
        (stream (gensym)))
    `(progn
       (defun ,writer-name (,file-name)
         (with-open-file (,stream ,file-name
                                  :direction :output :if-exists :supersede)
           (dolist (,var (list ,@vars))
             (declare (special ,@vars))
             (print ,var ,stream))))
       (defun ,reader-name (,file-name)
         (with-open-file (,stream ,file-name
                                  :direction :input :if-does-not-exist :error)
           (dolist (,var ',vars)
             (set ,var (read ,stream)))))
       t)))

Вот мой ха sh таблица и что происходит:

    (defvar *payees* (make-hash-table :test #'equal))

    (check-check 100.00 "Acme" "Rocket booster T-1000")

    CL-USER> *payees*
    #<HASH-TABLE :TEST EQUAL :COUNT 0 {25520F91}>

    CL-USER> (check-check 100.00 "Acme" "T-1000 rocket booster")
    #S(CHECK
    :NUMBER 100
    :DATE "2020-4-1"
    :AMOUNT 100.0
    :PAID "Acme"
    :MEMO "T-1000 rocket booster")
    CL-USER> (def-i/o save-checks charge-checks (*payees*))
    T
    CL-USER> (save-checks "/home/checks.dat")
    NIL
    CL-USER> (makunbound '*payees*)
    *PAYEES*

    CL-USER> (load-checks "/home/checks.dat")
; Evaluation aborted on #<SB-INT:SIMPLE-READER-ERROR "illegal sharp macro character: ~S" {258A8541}>.

В Lispworks сообщение об ошибке:

Error: subcharacter #\< not defined for dispatch char #\#.

Чтобы упростить, я получаю ту же ошибку, если я выполню:

CL-USER> (defvar *payees* (make-hash-table :test #'equal)) 
*PAYEES*
CL-USER> (with-open-file (in "/home/checks.dat"
                 :direction :input)
       (set *payees* (read in)))
; Evaluation aborted on #<SB-INT:SIMPLE-READER-ERROR "illegal sharp macro character: ~S" {23E83B91}>.

Может кто-нибудь объяснить мне, откуда возникла проблема, и что мне нужно исправить в моем коде, чтобы это работало. Заранее благодарю за объяснения, которые вы можете дать мне, и за помощь, которую вы можете оказать мне.

1 Ответ

5 голосов
/ 02 апреля 2020

Значения, которые не могут быть прочитаны, печатаются с помощью #<, см. Sharpsign Less-Than-Sign . Common Lisp не определяет, как ха sh -таблицы должны печататься читабельно (нет синтаксиса читателя для таблиц ха sh):

* (make-hash-table)
#<HASH-TABLE :TEST EQL :COUNT 0 {1006556E53}>

Пример в книге подходит только для использования с примером bank , который был показан ранее и не предназначен для использования в качестве механизма сериализации общего назначения.

Существует много способов напечатать таблицу ha sh и прочитать их обратно, в зависимости от ваших потребностей, но нет представления по умолчанию. Смотрите cl-store для библиотеки, которая хранит произвольные объекты.

Пользовательская dump функция

Давайте напишем форму, которая оценивает эквивалент га sh -столы; давайте определим обобщенную функцию c с именем dump и метод по умолчанию, который просто возвращает объект как есть:

(defgeneric dump (object)
  (:method (object) object))

При наличии таблицы ha sh мы можем сериализовать ее как plist (последовательность элементов ключ / значение в списке), при этом также вызывая dump для ключей и значений, в случае, если наши таблицы * sh содержат таблицы * sh:

(defun hash-table-plist-dump (hash &aux plist)
  (with-hash-table-iterator (next hash)
    (loop
       (multiple-value-bind (some key value) (next)
         (unless some
           (return plist))
         (push (dump value) plist)
         (push (dump key) plist)))))

Вместо того, чтобы заново изобретать колесо, мы могли бы также использовать hash-table-plist из системы alexandria .

(ql:quickload :alexandria)

Выше эквивалентно to:

(defun hash-table-plist-dump (hash)
  (mapcar #'dump (alexandria:hash-table-plist hash)))

Затем мы можем специализировать dump для таблиц ha sh:

(defmethod dump ((hash hash-table))
  (loop
     for (initarg accessor) 
     in '((:test hash-table-test)
          (:size hash-table-size)
          (:rehash-size hash-table-rehash-size)
          (:rehash-threshold hash-table-rehash-threshold))
     collect initarg into args
     collect `(quote ,(funcall accessor hash)) into args
     finally (return
               (alexandria:with-gensyms (h k v)
                 `(loop
                     :with ,h := (make-hash-table ,@args)
                     :for (,k ,v)
                     :on (list ,@(hash-table-plist-dump hash))
                     :by #'cddr
                     :do (setf (gethash ,k ,h) ,v)
                     :finally (return ,h))))))

Первая часть l oop вычисляет все га sh -табильные свойства, такие как тип используемой функции ha sh или reha sh -size, и строит args, список аргументов для вызова make-hash-table с теми же значениями.

В предложении finally мы строим выражение loop (см. Обратные кавычки), которое сначала выделяет таблицу ha sh, а затем заполняет ее в соответствии с текущей стоимостью Эс ха sh и, наконец, вернуть новый ха sh. Обратите внимание, что сгенерированный код не зависит от alexandria , его можно прочитать из другой системы Lisp, у которой нет этой зависимости.

Пример

CL-USER> (alexandria:plist-hash-table '("abc" 0 "def" 1 "ghi" 2 "jkl" 3)
                 :test #'equal)

#<HASH-TABLE :TEST EQUAL :COUNT 4 {100C91F8C3}>

Дамп it:

CL-USER> (dump *)
(LOOP :WITH #:H603 := (MAKE-HASH-TABLE :TEST 'EQUAL :SIZE '14 :REHASH-SIZE '1.5
                                       :REHASH-THRESHOLD '1.0)
      :FOR (#:K604 #:V605) :ON (LIST "jkl" 3 "ghi" 2 "def" 1 "abc"
                                     0) :BY #'CDDR
      :DO (SETF (GETHASH #:K604 #:H603) #:V605)
      :FINALLY (RETURN #:H603))

Сгенерированные данные также являются действительным кодом Lisp, оцените его:

CL-USER> (eval *)
#<HASH-TABLE :TEST EQUAL :COUNT 4 {100CD5CE93}>

Полученный ha sh равен equalp к исходному:

CL-USER> (equalp * ***)
T
...