结果如何将宏的列表参数转换为经过大量修改但引用的列表?

How do I turn a list argument to a macro into a heavily modified but quoted list as a result?

如何将宏的列表参数转换为经过大量修改但引用的列表?

我将 John Foster 将 win32 WM_* 消息转换为有用文本的示例翻译成 Lisp。 Convert Windows Message IDs to Text

我需要定义 win32 常量,一个宏似乎非常适合调用 (defconstant WM_PAINT 15) 并自动注册 WM_PAINT 和“WM_PAINT”。

工作测试代码:

(defmacro def-windows-message (&rest rest )
    "Converts a list of windows messages into defconstant and a string lookup in GetMessageText."
    `(block nil 
        ,@(loop for pair in rest collect
            `(defconstant ,(car pair) ,(cadr pair)))
        (defparameter *windows-message-lookup* #(
            ,@(loop for pair in rest collect (cons (cadr pair) (string (car pair) )))
        ))
        (defun GetMessageText (id) 
            (destructuring-bind (index text)
                (msgid_to_index id *windows-message-lookup* 0 (length *windows-message-lookup*))
                (if (= index -1)
                    (format nil "(WM_? ~a)" id)
                    ; else
                    text)))
        (defun msgid_to_index (target_id array min_idx max_idx)
            (let* ( (cur_idx (floor (/ (+ max_idx min_idx) 2)))
                    (cur_pair (aref array cur_idx))
                    (cur_id (car cur_pair))
                    (cur_text (cdr cur_pair)))
                (cond
                    ((= target_id cur_id) (list cur_idx cur_text))
                    ((> target_id cur_id) 
                        (if (= cur_idx min_idx)
                            '(-1 nil)
                            ; else
                            (msgid_to_index target_id array cur_idx max_idx)))
                    ((< target_id cur_id) 
                        (if (= cur_idx max_idx)
                            '(-1 nil)
                            ;else
                            (msgid_to_index target_id array min_idx cur_idx)))
                )
            )
        )
    ))

; Test it!
(def-windows-message
    (WM_NULL 0)
    (WM_CREATE 1))
(format t "~a~%" (GetMessageText 0))
(format t "~a~%" (GetMessageText 1))
(format t "~a~%" (GetMessageText -1))
(format t "~a~%" (GetMessageText 99))

工作输出:

C:\lisphack>clisp test_macro_long_list.lisp
WM_NULL
WM_CREATE
(WM_? -1)
(WM_? 99)

非常好。这正是我想要的。

但是,当我向列表中添加 240 多条 windows 条消息时,它失败了。

长列表失败输出*** - VALUES-LIST: too many return values

此消息表示传递给 #() 函数的函数参数过多。

我最终只创建了一个空数组,然后循环遍历列表,将每个元素插入到数组中。

工作但粗略的片段:

(defparameter *windows-message-looup* (make-array ,(length rest) :initial-element '(-1 Nil)))
    ,@(loop for pair in rest for idx from 0 collect `(setf (aref *windows-message-lookup* ,idx) (cons ,(cadr pair) ,(string (car pair) ))))

我真正想要的是将 &rest 列表转换为正确的形式,然后将该列表作为初始值发送到数组构造函数中:

优雅但有瑕疵

(defparameter *windows-message-lookup* (make-array ,(length rest) 
    ,@(loop for pair in rest collect (cons (cadr pair) (string (car pair) )))

错误: *** - EVAL: 0 is not a function name; try using a symbol instead 我认为这意味着 make-array 的最终参数正在执行而不是作为初始化程序引用。

我需要在结果列表中添加一个额外的引号。 再引述

(defparameter *windows-message-lookup* (make-array ,(length rest) 
    `,@(loop for pair in rest collect ,(cons (cadr pair) (string (car pair) )))))

错误: *** - READ: the syntax ,@表单无效`

我尝试了一些变体,添加额外的反引号或移动括号,但它们导致我的列表或参数对的列表执行而不是读取。

[编辑] 谢谢。这看起来比我尝试的要好。对于我的教育,这个宏的正确形式是什么?

(defmacro def-windows-message (&rest rest )
    `(defparameter *windows-message-lookup* 
        (make-array 
            ,(length rest) 
            `(,@(loop for pair in rest collect ,(cons (cadr pair) (string (car pair) )))))))

对于(def-windows-message '((onefish 1) (twofish 2) .. ))的输入 我想要 (make-array 999 '( (onefish "onefish") (twofish "twofish") ... )) 作为结果。

我在正确引用结果列表并仍在宏中处理它时遇到问题。

我试图理解这个宏在做什么,但当我意识到它在数组上使用二进制搜索来查找消息时就放弃了。这就是为什么将 C 语言翻译成 Lisp 语言几乎从来都不是正确答案的原因。

我不太确定,但我想你可能想要这个:

(defvar *windows-message-lookup*
  ;; Unless you have vast numbers of messages (thousands) and/or the
  ;; code is very performance sensitive, this could as well be an
  ;; alist
  (make-hash-table))

(defmacro def-windows-messages (&body messages)
  "Define a bunch of windows messages, each message is (name id)"
  `(progn
    ,@(loop for (name id) in messages
            collect `(defconstant ,name ,id)
            collect `(setf (gethash ,id *windows-message-lookup*)
                           (string ',name)))
    (values)))

(defun get-message-text (id) 
  "Return the text associated with ID and T if it's found, or a string
saying it's not found and NIL if it's not found."
  (multiple-value-bind (text foundp)
      (gethash id *windows-message-lookup*)
    (values (if foundp text (format nil (format nil "(WM_? ~a)" id)))
            foundp)))

现在:

> (def-windows-messages
    (WM_NULL 0)
    (WM_CREATE 1))

> (get-message-text 0)
"WM_NULL"
t

> (get-message-text 1)
"WM_CREATE"
t

> (get-message-text -1)
"(WM_? -1)"
nil

> (get-message-text 99)
"(WM_? 99)"
nil

请注意,现在的代码(之前略有不同)可以这样说

(defconstant some-id 12)
(define-windows-messages
  (WM_FOO some-id))

作为原始宏中引用问题的一个示例,这里有一个名为 define-message-array 的宏,它可用于以与原始宏类似的方式定义消息数组。它与原始宏的不同之处在于您可以指定数组的名称。

(defmacro define-message-array (name &body from)
  `(defparameter ,name
     (make-array ,(length from) 
                 :initial-contents
                 '(,@(loop for (n v) in from
                           collect (cons v (string n)))))))

请注意,这里没有嵌套的反引号:您并不经常需要它。

有了这个,那么

(define-message-array *foo*
  (x 1)
  (y 2))

扩展到

(defparameter *foo*
  (make-array 2 :initial-contents '((1 . "X") (2 . "Y"))))