Есть ли способ смешать макросы LISP, а также дополнительные и ключевые параметры? - PullRequest
4 голосов
/ 26 июня 2019

Я хочу определить макрос LISP, такой как dolist, который позволяет мне определять необязательный выходной аргумент. В следующем примере этот макрос будет называться doread. Он будет читать строки из файла и возвращать количество найденных строк таким образом.

(let ((lines 0))
  (doread (line file lines)
     ;; do something with line
     (incf lines)))

Проблема в том, что lines работает в приведенном выше макросе

Я могу делать то, что хочу, с & ключом, но не с & необязательным & ключом (и ключ & необходим, так как я хочу контролировать, как читается файл; например, с read или read-line или любым другим).

Теперь следующее работает НО , чтобы работать неправильно. Здесь аргумент out должен быть &key, а не & необязательным:

;; this way works... 

(defmacro doread ((it f  &key out (take #'read)) &body body)
  "Iterator for running over files or strings."
  (let ((str (gensym)))
    `(with-open-file (,str f)
       (loop for ,it = (funcall ,take ,str nil)
             while ,it do
             (progn ,@body))
       ,out)))

;; lets me define something that reads first line of a file
(defun para1 (f)
  "Read everything up to first blank line."
  (with-output-to-string (s)
    (doread (x f :take #'read-line)
      (if (equalp "" (string-trim '(#\Space #\Tab) x))
        (return)
        (format s "~a~%" x)))))

(print (para1 sometime)) ; ==> shows all up to first blank line

Я хотел бы сделать следующее: обратите внимание, что out теперь переместился в &optional:

(defmacro doread ((it f &optional out &key   (take #'read)) &body body)
  "Iterator for running over files or strings."
  (let ((str (gensym)))
    `(with-open-file (,str f)
       (loop for ,it = (funcall ,take ,str nil)
             while ,it do
             (progn ,@body))
       ,out)))

и если бы это сработало, я мог бы сделать что-то вроде.

(defun para1 (f)
  "Print  everything up to first blank line. 
   Return the lines found in that way"
  (let ((lines 0))
      (doread (x f lines :take #'read-line)
        (if (equalp "" (string-trim '(#\Space #\Tab) x))
            (return)
            (and (incf lines) (format t "~a~%" x)))))

но это я использую &optional out я получаю

 loading /Users/timm/gits/timm/lisp/src/lib/macros.lisp
*** - GETF: the property list (#'READ-LINE) has an odd length

Ответы [ 2 ]

6 голосов
/ 26 июня 2019

Вы не можете смешивать &optional и &key и ожидать, что сможете передавать только аргументы ключевого слова.Однако вы можете определить синтаксис, который допускает необязательный список аргументов, связанных с источником.

Например:

(defpackage :so (:use :cl :alexandria))
(in-package :so)

(defmacro do-read ((entry source &optional result) &body body)
  (destructuring-bind (source &key (take '#'read)) (ensure-list source)
    (once-only (take)
      `(loop
          :with ,entry
          :do (setf ,entry (handler-case (funcall ,take ,source)
                             (end-of-file () (loop-finish))))
            (progn ,@body)
          :finally (return ,result)))))

Синтаксис для DO-READ может быть записан как:

(DO-READ (ENTRY [SOURCE|(SOURCE &KEY TAKE)] &OPTIONAL RESULT) . BODY)

Это не необычный синтаксис относительно стандартных форм Lisp (см. LET, ключевое слово synax в лямбда-списках, defstruct и т. Д.).Вы можете добавить дополнительные параметры ключевых слов вместе с TAKE.

Заметки

  • В макросах я предпочитаю использовать ключевые слова LOOP в качестве ключевых слов, а не символов в пакете определения макроса;в противном случае при макроэкспандировании кода вы, вероятно, получите символы с префиксом пакета макроса (т. е. SO::WITH вместо :WITH), который быстро становится нечитаемым.

  • Возвращая NILс READ-LINE нормально, но не с READ, так как NIL может быть успешно прочитанным значением.В общем, поскольку пользователь предоставляет TAKE, вы не знаете, является ли NIL приемлемым результатом или нет.Вот почему я ловлю END-OF-FILE вместо этого.Если вы хотите читать из других источников, вы также можете проверить вторичное возвращаемое значение или документ, подтверждающий, что они тоже сигнализируют об условии.

  • Область действия переменной ENTRY расширена, так что RESULT может быть ENTRY само по себе;в вашем случае OUT не может быть равным IT, потому что, как только вы выйдете из цикла, у вас больше не будет к нему доступа.Это второстепенный вопрос, но он может быть полезен.

  • Я не включил WITH-OPEN-FILE, на случай, если вы хотите читать что-то, кроме файлов (потоков).

  • #'READ цитируется, это не важно здесь, но хорошая привычка иметь в макросах, так что вы действительно оцениваете вещи во время оценки, а не во время макроразложения.

Примеры

(with-input-from-string (in "abcdef")
  (do-read (char (in :take #'read-char) char)
    (print char)))

Вывести все символы и вернуть #\f.

(with-input-from-string (in (format nil "~{~a~%~}" *features*))
  (let ((lines 0))
    (do-read (line in lines)
      (incf lines))))

Распечатать количество строк в строке.

3 голосов
/ 27 июня 2019

у меня работает:

(defmacro doread ((it f &optional out &key (take #'read)) &body body)
  "Iterator for running over files or strings."
  (let ((str (gensym)))
    `(with-open-file (,str ,f)
       (loop for ,it = (funcall ,take ,str nil)
             while ,it do
             (progn ,@body))
       ,out)))

(defun para1 (f)
  "Print  everything up to first blank line. 
   Return the lines found in that way"
  (let ((lines 0))
    (doread (x f lines :take #'read-line)
      (if (equalp "" (string-trim '(#\Space #\Tab) x))
          (return)
        (and (incf lines) (format t "~a~%" x))))))

Использование в LispWorks:

CL-USER 104 > (para1 (capi:prompt-for-file "text file"))
;;; -*- mode: Lisp; Base: 10 ; Syntax: ANSI-Common-Lisp ; buffer-read-only: t; -*-
;;; This is ASDF 3.3.3: Another System Definition Facility.
;;;
;;; Feedback, bug reports, and patches are all welcome:
;;; please mail to <asdf-devel@common-lisp.net>.
;;; Note first that the canonical source for ASDF is presently
;;; <URL:http://common-lisp.net/project/asdf/>.
;;;
;;; If you obtained this copy from anywhere else, and you experience
;;; trouble using it, or find bugs, you may want to check at the
;;; location above for a more recent version (and for documentation
;;; and test files, if your copy came without them) before reporting
;;; bugs.  There are usually two "supported" revisions - the git master
;;; branch is the latest development version, whereas the git release
;;; branch may be slightly older but is considered `stable'
15

Просто если вы хотите указать :take, вам также необходимо указать необязательный аргумент arg. Это распространенная ошибка, и поэтому комбинация необязательных аргументов и аргументов с ключевыми словами не нравится.

...