lisp宏中的符号操作

时间:2015-09-15 21:46:37

标签: macros common-lisp symbols

我正在为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问题,但我不确定如何处理这个问题。

2 个答案:

答案 0 :(得分:4)

|...|语法只是Lisp打印机可以打印其名称中需要转义的字符的符号的一种方式(并且读者可以读取符号名字中的那些字符):

(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-downcase 需要字符串指示符,而不仅仅是字符串,所以您可以直接传递符号:

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?))

在生成符号名称时避免使用格式通常是个好主意,因为具体的输出可能会根据其他变量而改变。 E.g:

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

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

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

这样,如果读者做了任何不寻常的事情(例如,保留大小写,以便mal-的符号名称为"mal-),您也可以将其保存在您自己生成的符号中。< / p>

答案 1 :(得分:3)

除了Joshua's detailed answer之外,请考虑使用Alexandria库中的函数:

  • format-symbolformat类似,但在with-standard-io-syntax内。在这里,t代表当前包,而名称是下载的:

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

    (symbolicate '#:mal- name)
    

    如果您当前的可读表格保留不存在,您最终可以使用|MAL-LIST||mal-list|。为了完整起见,请注意readtable-case可以设置为following values:upcase:downcase:preserve:invert(我觉得这个有趣)。

相关问题