LINUX.ORG.RU

[common lisp][macro characters]Как читает лисп следующее выражение?

 


0

1

Значит была у меня задача: Сделать с некоторыми 2 аргументами простую задачу (посмотреть что-то в базе данных и выполнить простые расчеты).

Это всё сводилась к вызову функции do-something:

(do-something a b)

Теперь мне надо сделать так, чтобы когда я в REPL ввожу [a b c ... n] (итд до n), вызывались функции:

(do-something a b)
(do-something a c)
...
(do-something a n)

Ну а возвращаемые значения, скажем, собирались в лист.

Что я делаю:

(defun call-do-something (stream char)
    (let* ((lst (read-delimited-list #\] stream))
           (firstarg (first lst))
           (rest (cdr lst)))
       `(loop for i in ',rest collect (do-something ,firstarg i))))

(set-macro-character #\[ #'call-do-something)

При вводе конструкции типа [a b c ... n] в REPL, лисп ждет что-то дальше, как если бы я ввел:

( 54 54 55 4 4

При определении символа #\] как макро-символа:

(set-macro-character #\] (get-macro-character #\)))

Всё работает.

Вопрос: Что же закрывает ] ?

Вот здесь:

http://www.ida.liu.se/imported/cltl/clm/node188.html#SECTION002611000000000000000

в пункте 3 написанно, что если функция, связанная с макросимволом возвращает значение, то the algorithm is done. То есть, как я понял, он возвращает loop-конструкцию и заканчивает работу. А откуда тогда ещё открытая скобка берется?

P.S. Да, возможно моя терминология хромает и я где-то назвал собаку кошкой. Поправляйте меня)

Ответ на: комментарий от archimag

Да это отчасти надуманный пример, чисто теоретически спрашиваю, но НО.

А в maxima они как REPL сделали? Да и вообще, вдруг синтаксис поменять понадобиться)

different_thing
() автор топика
Ответ на: комментарий от korvin_

Giving } the same definition as the standard definition of the character ) has the twin benefit of making it terminate tokens for use with read-delimited-list and also making it invalid for use in any other context. Attempting to read a stray } will signal an error.

Ага, в первый раз не заметил что-то

different_thing
() автор топика
Ответ на: комментарий от korvin_

Всё теперь понял всё оканчательно (кажется).

Главное, чтобы ] был макросимволом, тогда read-delimited-list сможет увидеть его как отдельный символ. При этом связь в #\) видимо дается затем, чтобы #\] не мог быть использован без #\[, так?

different_thing
() автор топика
Ответ на: комментарий от different_thing

Главное, чтобы ] был макросимволом, тогда read-delimited-list сможет увидеть его как отдельный символ. При этом связь в #\) видимо дается затем, чтобы #\] не мог быть использован без #\[, так?

Нет, просто read-delimited-list хочет чтобы скобка была отдельно от токена:

(read-delimited-list #\])
1 2 3]] ]
=> (1 2 3]])

(read-delimited-list #\])
1 2 3 ]
=> (1 2 3)

Макро-символом должен быть только первый символ, всё остальное ложится на пользовательскую функцию чтения.

Всё же если нужен другой синтаксис - нужно делать другой синтаксис, а не менять ридер. При этом в начале написать без «фич», например в данном случае нужно вот что:

;;; The EACH macro.
;;;
(defmacro each ((element list) &body body)
  `(mapcar #'(lambda (,element)
               ,@body)
           ,list))

;;; The MANY macro.
;;;
(defmacro many (thing &rest forms)
  `(values
    ,@(each (form forms)
        (if (atom form)
            (if (atom thing)
                `(,thing ,form)
                `(,@thing ,form))
            (if (atom thing)
                `(,thing ,@form)
                `(,@thing ,@form))))))

;;; What is MANY:
;;;
;;;  (macroexpand-1 '(many (something a) b c d))
;;;  => (VALUES (SOMETHING A B) (SOMETHING A C) (SOMETHING A D))
;;;
;;;  (macroexpand-1 '(many defvar (a 1) (b 2) (c 3)))
;;;  => (VALUES (DEFVAR A 1) (DEFVAR B 2) (DEFVAR C 3))
;;;

(defun something (&rest args)
  (reduce #'+ args))

(defun gen-something-body (numbers)
  `(many (something ,(first numbers))
     ,@(rest numbers)))

(defmacro something-body (&rest numbers)
  (gen-something-body numbers))

;;;
;;; (macroexpand-1 (gen-something-body '(1 2 3)))
;;; => (VALUES (SOMETHING 1 2) (SOMETHING 1 3))
;;;
;;; (macroexpand-1 '(something-body 1 2 3))
;;; => (MANY (SOMETHING 1) 2 3)
;;;
;;; (macroexpand '(something-body 1 2 3))
;;; => (VALUES (SOMETHING 1 2) (SOMETHING 1 3))
;;;
;;; (something-body 1 2 3)
;;; =>
;;; (1 2)
;;; (1 3)
;;;

Так как read-delimited-list не работает, написать свою процедуру чтения таких списков:

(defun read-square-list (&optional (input-stream *standard-input*))
  (let ((first-char (read-char input-stream))
        result-list)
    (assert (char= first-char #\[))
    (loop
      (let ((token (read input-stream)))
        (when (eq token '|]|)
          (return-from read-square-list (nreverse result-list)))
        (if (symbolp token)
            (let ((string (string token)))
              (when (and (not (string= string ""))
                         (char= (char string (1- (length string))) #\]))
                (return-from read-square-list
                  (append
                   (nreverse result-list)
                   (list (read-from-string (subseq string 0 (1- (length string)))))))))
            (push token result-list))))))

(defun read-square-list-from-string (string)
  (with-input-from-string (stream string)
    (read-square-list stream)))

;;;
;;; (read-square-list-from-string "[1 (2) #(3)]")
;;; => (1 (2) #(3))
;;;

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

(set-macro-character
 #\[
 #'(lambda (stream char)
     (declare (ignore char))
     (gen-something-body (read-square-list stream))))

;;;
;;; [1 2 3 4 5]
;;; =>
;;; 3
;;; 4
;;; 5
;;; 6
;;;

Хотя тут бы ещё неплохо обеспечить бесконфликтность с другими таблицами чтения.

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto
 (defun read-square-list (&optional (input-stream *standard-input*))
-  (let ((first-char (read-char input-stream))
-        result-list)
-    (assert (char= first-char #\[))
+  (let (result-list)
     (loop
       (let ((token (read input-stream)))
quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

> Нет, просто read-delimited-list хочет чтобы скобка была отдельно от токена:

Ну так оно вроде отдельно и воспримет его. Можно конечно без всяких макросимволов написать [ 5 5 76 8 545454 ], но объявление его макросимволом вроде как гарантирует отдельность чтения даже в случае 545454], так я понял

different_thing
() автор топика
Ответ на: комментарий от quasimoto

> (read-delimited-list #\]) 1 2 3 ] => (1 2 3)

Ну да, там как раз пример есть, что если whitespace (пробел?) перед ] поставить, то всё будет ok

different_thing
() автор топика
Ответ на: комментарий от different_thing

если вбить «1 2 3]» оно будет ждать следующий «]» отдельно, при этом последним в списке будет «3]». Так что нужно свою процедуру писать - либо попроще (как у меня с помощью read) либо посложней. Но лучше попроще - так в квадратном списке могут быть элементы самго разного типа.

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

Хотя можно и ] прописать как макро символ:

(set-macro-character #\] (get-macro-character #\)))
(set-macro-character
 #\[
 #'(lambda (stream char)
     (declare (ignore char))
     (gen-something-body (read-delimited-list #\] stream))))

;;;
;;; [1 2 3 4 5]
;;; =>
;;; 3
;;; 4
;;; 5
;;; 6
;;;
quasimoto ★★★★
()
Ответ на: комментарий от korvin_

почему не работает?

Работает, сорри. Но при изменённой таблице чтения. Так что в стандартной поставке - не работает :)

quasimoto ★★★★
()

Спасибо всем за помощь, понял теперь.

different_thing
() автор топика
Ответ на: комментарий от quasimoto

> Работает, сорри. Но при изменённой таблице чтения. Так что в стандартной поставке - не работает :)

он работает именно так, как ему положено

korvin_ ★★★★★
()
Ответ на: комментарий от korvin_

он работает именно так, как ему положено

Согласен. Не читал стандарта в том месте.

quasimoto ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.