问一个 elisp 宏的问题

因为同时会在 macOS 和 Windows 上使用 Emacs,配置文件里不少会出现类似

(defvar id
  (pcase system-type
    ('windows-nt some-value)
    ('darwin another-value)))

这种情况。于是想写一个宏(稍微能)缩短一点这种写法。其实主要是想学习一下如何写 elisp macro

宏是这样:

(defvar/os id :windows some-value :macos another-value)

展开后成为前面的样子。另外,希望 :windows some-value 这些 clause 是 optional 的。

请问要如何实现这样的宏?

1 个赞

&rest plist 接受任意数量参数,然后当作 Plist 处理。不熟悉 Macro 可以先写个函数,然后再改。

(defmacro defvar/os (id &rest args)
  (let ((arglen (length args))
        plist)
    (unless (and (evenp arglen)
		 (> arglen 0))
      (error "wrong number of arguments: %s" arglen))
    (while args
      (push (list
             (pcase (pop args)
               (:windows ''windows-nt)
               (:macos ''darwin)
               (:linux ''gnu/linux))
             (pop args))
            plist)
      )
    `(defvar ,id
       (pcase system-type
	 ,@plist
	 ))))

就这个需求本身来说,还是用函数比较简洁方便: (没测试)

(defun by-system (config)
  (plist-get config
             (plist-get system-type
                        '(windows-nt
                          :windows
                          darwin :macos))))

(defmacro defvar/os (symbol &rest config)
  `(defvar ,symbol (by-system ,config)))

另外一般配置里用setqdefvar不会覆盖原值。

-             (plist-get system-type
-                        '(windows-nt
-                          :windows
-                          darwin :macos))))
+             (plist-get '(windows-nt :windows
+                          darwin :macos)
+                        system-type)))

帮你改下。


(defmacro defvar/os (name &rest args)
  `(defvar ,name
     (pcase system-type
       ,@(mapcar (lambda (x)
                   (let ((a (car x))
                         (b (cadr x)))
                     `(,(ecase a
                          (:windows ''windows-nt)
                          (:macos ''darwin))
                       ,b)))
                   (-partition-all 2 args)))))
2 个赞