lisp宏中的符号操作

Symbol manipulation in lisp macro

我正在为 Lisp 语言编写玩具解释器,其中我有以下 CL 代码:

(defun mal-list (&rest args)
  (make-mal :type 'list
            :value args))
(register-fun '|list| #'mal-list)

(defun mal-list? (arg)
  (eq (mal-type arg) 'list))
(register-fun '|list?| #'mal-list?)

但是,我宁愿简单地写这样的东西:

(defmal list (&rest args)
   (make-mal :type 'list
             :value args))

(defmal list? (arg)
   (eq (mal-type arg) 'list))

我试图编写一个宏来执行此操作,但我对带条的符号有疑问(我很困惑这是什么!)。这是我试过的:

(defmacro defmal (name args &body body )
  (let ((funsym (intern (format nil "~{~a~}" `(mal- ,name)))))
    `(register-fun `|,name| (defun ,funsym ,args ,@body))))

没有解决,因为 `|,name| 字面上的意思是 |,name|,而不是 |list|

我猜这是一个 XY 问题,但我不确定如何解决这个问题。

|...| 语法只是 Lisp 打印机打印名称中包含需要转义字符的符号的方式之一(并且 reader 可以 阅读 名称中含有这些字符的符号):

(print (intern "foo"))
;=> |foo| 

还有其他方法,包括转义单个字符:

(print '|FOO|)
;=> FOO

(print '\f\o\o)
;=> |foo| 

您要做的只是创建一个名称包含小写字母的符号。这很简单,如上所示。不过,您的部分问题是您输入的是一个名称全是大写字母的符号,因此您需要先小写:

CL-USER> (symbol-name 'FOO)
;=> "FOO"

CL-USER> (intern (symbol-name 'FOO))
;=> FOO

CL-USER> (string-downcase (symbol-name 'FOO))
;=> "foo"

CL-USER> (intern (string-downcase (symbol-name 'FOO)))
;=> |foo|

其实,因为string-downcasestring designators,不只是字符串,可以直接传入符号:

CL-USER> (intern (string-downcase 'BaR))
;=> |bar|

所以,在所有字符串处理之后,我们可以转到宏。


听起来您正在寻找这样的东西:

(defmacro defmal (name lambda-list &body body)
  (let ((mal-name (intern (concatenate 'string "MAL-" (symbol-name name))))
        (mal-norm (intern (string-downcase name))))
    `(progn
       (defun ,mal-name ,lambda-list
          ,@body)
       (register-function ',mal-norm #',mal-name))))

CL-USER> (pprint (macroexpand-1 '(defmal list? (arg)
                                  (eq (mal-type arg) 'list))))

(PROGN
 (DEFUN MAL-LIST? (ARG) (EQ (MAL-TYPE ARG) 'LIST))
 (REGISTER-FUNCTION '|list?| #'MAL-LIST?))

在生成符号名称时避免使用 format 通常是个好主意,因为特定的输出可能会发生变化,具体取决于其他变量。例如:

(loop for case in '(:upcase :downcase :capitalize)
   collect (let ((*print-case* case))
             (format nil "~a" 'foo)))
;=> ("FOO" "foo" "Foo")

相反,您可以使用 concatenate 与字符串(或符号的符号名称)。因为 reader 也可以有不同的区分大小写设置,有时我什至会这样做(但不是每个人都喜欢这样):

(concatenate 'string (symbol-name '#:mal-) (symbol-name name))

这样,如果 reader 做了任何不寻常的事情(例如,保留大小写,因此 mal- 的符号名称是 "mal-),您可以将其保留在您自己的也生成符号。

除了, consider using a function from the Alexandria图书馆:

  • format-symbol 就像 format, but inside with-standard-io-syntax。这里,t代表当前包,name是小写的:

    (format-symbol t "mal-~(~A~)" name)
    => |mal-list|
    
  • symbolicate 在当前包中连接和实习生:

    (symbolicate '#:mal- name)
    

    如果您当前的可读表是否保留大小写,您可以以 |MAL-LIST||mal-list| 结束。为了完整起见,请注意 readtable-case 可以设置为 following values:upcase:downcase:preserve:invert(这个我觉得相当有趣)。