Макрос, который генерирует форму макроса, завернутую в связанные переменные let

Я пытался написать макрос, который генерирует, среди прочего, одноименный макрос компилятора. Это минимальный код, с которым я застрял:

 (defmacro definline (name lambda-list &body body)
  `(define-compiler-macro ,name ,lambda-list
     `(let ,,(mapcar (lambda (v) ``(,',v ,,v)) lambda-list)
        ,,@body)))

Я хочу что-то вроде этого:

(definline foobar (a b) (print "foobar") (+ a b))
;; Expands to
(define-compiler-macro foobar (a b)
  `(let ((a ,a) (b ,b))
     (print "foobar") (+ a b)))

Но я не могу понять, как генерировать привязки let ((a ,a) (b ,b)). Проблема, которую я не смог понять, заключается в том, как сгенерировать форму макроса компилятора, чтобы содержимое привязок лямбда было без кавычек в расширении. Я понимаю, как это сделать вручную, но я не уверен, как это сделать в общем для произвольного лямбда-списка.

Изменить:

Еще немного повозившись, я придумал это. Который работает. Но, ну, это отвратительно.

(defmacro definline (name lambda-list &body body)
  (read-from-string
   (format nil "(define-compiler-macro ~S (~{~S~^ ~})
                    `(let (~{(~S ,~S)~})
                       ~{~S~}))"
           name lambda-list (loop for l in lambda-list nconc (list l l)) body)))

person asm    schedule 18.02.2014    source источник


Ответы (1)


Подход с использованием format вообще не работает, если некоторые переменные, связанные с вводом-выводом, изменены. Использование format для генерации имен символов, как правило, довольно хрупкое и непереносимое. Однако эту это трудную проблему решить, и может быть проще решить ее с помощью построения списка, а не только обратных кавычек. Например, в этом случае мы могли бы иметь:

(defmacro definline (name variables &body body)
  (list 'define-compiler-macro name variables
        `(list* 'let (list ,@(mapcar (lambda (variable)
                                       `(list (quote ,variable) ,variable))
                              variables))
                ',body)))

Это работает так, что:

CL-USER> (pprint (macroexpand-1 '(definline foobar (a b) 
                         (print "foobar")
                         (+ a b))))

(DEFINE-COMPILER-MACRO FOOBAR
    (A B)
  (LIST* 'LET (LIST (LIST 'A A) (LIST 'B B)) '((PRINT "foobar") (+ A B))))

который, если я что-то неправильно понимаю, должен иметь те же результаты, что и:

(define-compiler-macro foobar (a b)
  `(let ((a ,a) (b ,b))
     (print "foobar")
     (+ a b)))

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

(define-compiler-macro foobar (a b)
  (backquote (let ((a (unquote a))
                   (b (unquote b)))
               (print "foobar")
               (+ a b)))

Если ваша реализация действительно реализует это таким образом, вы можете написать расширение, которое генерирует этот тип вывода. Если вы не получите такую ​​гарантию от своей реализации, я не думаю, что есть способ получить «переменную запятой», которую вам нужно ввести в слой выше. Трудно сделать это ясно, но вы можете посмотреть на эту попытку:

(defmacro definline (name variables &body body)
  `(define-compiler-macro ,name ,variables
     `(let ,',(mapcar (lambda (variable)
                        `(,variable (unquote ,variable)))
                      variables)
        ,@',body)))

Это даст такие результаты, как:

(DEFINE-COMPILER-MACRO FOOBAR
    (A B)
  '(LET ((A (UNQUOTE A)) (B (UNQUOTE B)))
     (PRINT "foobar")
     (+ A B)))

Обратите внимание, что SBCL уже достаточно умен, чтобы заменить обратную кавычку обычной кавычкой, так как внутри нет ничего, что нужно было бы снимать с кавычек. mapcar, генерирующий форму, которая встраивается, не может генерировать код с запятыми в нем, потому что не указано, как эти запятые реализованы, и, согласно 2.4.7 Запятая, "запятая недействительна, если используется не внутри тела выражения обратной кавычки". Я думаю, это означает, что ваш лучший вариант что-то вроде:

(defmacro definline (name variables &body body)
  `(define-compiler-macro ,name ,variables
     `(let ,(mapcar 'list 
                    ',variables
                    (list ,@variables))
        ,@',body)))

Расширение этого будет отличаться в разных реализациях, но в SBCL это:

(DEFINE-COMPILER-MACRO FOOBAR (A B)
  `(LET (SB-IMPL::BACKQ-COMMA (MAPCAR 'LIST '(A B) (LIST A B)))
     (PRINT "foobar")
     (+ A B)))

В CCL вы получаете:

(DEFINE-COMPILER-MACRO FOOBAR (A B)
  (LIST* 'LET
         (LIST* (MAPCAR 'LIST '(A B) (LIST A B))
                '((PRINT "foobar") (+ A B)))))

В КЛИСПЕ:

(DEFINE-COMPILER-MACRO FOOBAR (A B)
 (CONS 'LET
  (CONS (MAPCAR 'LIST '(A B) (LIST A B)) '((PRINT "foobar") (+ A B)))))
person Joshua Taylor    schedule 19.02.2014
comment
Вызов сгенерированной макрофункции компилятора, например. (funcall (компилятор-макрофункция 'foobar) '(foobar 10 11) ()) может быть полезным инструментом для проверки вывода здесь в дополнение к macroexpand-1. - person m-n; 19.02.2014