我正在为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问题,但我不确定如何处理这个问题。
答案 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-symbol
与format
类似,但在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
(我觉得这个有趣)。