通常当我尝试编写一个宏时,我遇到了以下困难:我需要一个传递给宏的表单,然后在生成宏扩展时调用的辅助函数处理之前进行评估.在下面的示例中,我们只关心如何编写宏来发出我们想要的代码,而不是宏本身的无用:
想象一下(忍受我)一个Common Lisp lambda宏的版本,其中只有参数的数量很重要,参数的名称和顺序不是.我们称之为jlambda.它会像这样使用:
(jlambda 2
...body)
Run Code Online (Sandbox Code Playgroud)
2返回函数的arity 在哪里.换句话说,这会产生二元运算符.
现在想象一下,给定arity,jlambda产生一个虚拟的lambda列表,它传递给实际的lambda宏,如下所示:
(defun build-lambda-list (arity)
(assert (alexandria:non-negative-integer-p arity))
(loop for x below arity collect (gensym)))
(build-lambda-list 2)
==> (#:G15 #:G16)
Run Code Online (Sandbox Code Playgroud)
上述调用的扩展jlambda将如下所示:
(lambda (#:G15 #:16)
(declare (ignore #:G15 #:16))
…body))
Run Code Online (Sandbox Code Playgroud)
假设我们需要jlambda宏能够以一个Lisp形式接收arity值,该形式计算为非负整数(而不是直接接收非负整数),例如:
(jlambda (+ 1 1)
...body)
Run Code Online (Sandbox Code Playgroud)
(+ 1 1)需要对表单进行求值,然后需要将结果传递给build-lambda-list需要进行评估的结果,并将结果插入到宏扩展中.
(+ 1 1)
=> 2
(build-lambda-list 2)
=> (#:G17 #:18)
(jlambda (+ 1 1) ...body)
=> (lambda (#:G19 #:20) …Run Code Online (Sandbox Code Playgroud) *注意:尽管已经经常使用StackOverflow,但这是我自己发布的第一个问题.道歉,如果它有点冗长.建设性批评赞赏.
当我使用defstruct在Common Lisp中定义结构时,会自动生成一个谓词函数,用于测试其参数是否为defstruct定义的类型.例如:
(defstruct book
title
author)
(let ((huck-finn (make-book :title "The Adventures of Huckleberry Finn" :author "Mark Twain")))
(book-p huck-finn))
=> True
Run Code Online (Sandbox Code Playgroud)
但是,在使用defclass定义类时,这些函数似乎不是默认生成的(有没有办法指定这个?),所以我试图自己添加这个功能,因为我想a)这个语法来在结构和类之间保持一致,b)有一个缩写(typep obj 'classname),我需要经常写,并且视觉上很吵,c)作为编程练习,因为我还是比较新的Lisp.
我可以编写一个宏来定义一个给定类名称的谓词函数:
(defclass book ()
((title :initarg :title
:accessor title)
(author :initarg :author
:accessor author)))
;This...
(defmacro gen-predicate (classname)
...)
;...should expand to this...
(defun book-p (obj)
(typep obj 'book))
;...when called like this:
(gen-predicate 'book)
Run Code Online (Sandbox Code Playgroud)
我需要传递给defun的名称必须是格式'classname-p.这是我遇到困难的地方.要创建这样一个符号,我可以使用Paul Graham的On Lisp中的"symb"函数(第58页).当它在REPL上运行时:
(symb 'book '-p)
=> BOOK-P
Run Code Online (Sandbox Code Playgroud)
到目前为止,我的gen-predicate宏看起来像这样:
(defmacro gen-predicate (classname)
`(defun ,(symb classname …Run Code Online (Sandbox Code Playgroud) 我正在用Common Lisp编写一个程序,用于编辑由Minecraft生成的使用NBT格式的二进制文件,这里有文档记录:http://minecraft.gamepedia.com/NBT_format?cookieSetup = true (我知道存在这样的工具,比如NBTEditor和MCEdit,但两者都不是用Common Lisp编写的,我认为这个项目将是一个很好的学习练习.
到目前为止,我自己没有设法实现的唯一一项功能是读取已知长度的UTF-8字符串,该字符串包含使用多个八位字节(即非ASCII字符)表示的字符.在NBT格式中,每个字符串都是UTF-8编码的,并且前面是一个短(两个八位字节)整数,n表示字符串的长度.因此,假设字符串中只存在ASCII字符,我可以简单地n从流中读取一个八位字节序列,并使用类似的方式将其转换为字符串:
(defun read-utf-8-string (string-length byte-stream)
(let ((seq (make-array string-length :element-type '(unsigned-byte 8)
:fill-pointer t)))
(setf (fill-pointer seq) (read-sequence seq byte-stream))
(flexi-streams:octets-to-string seq :external-format :utf-8)))
Run Code Online (Sandbox Code Playgroud)
但是,如果一个或多个字符的字符代码大于255,则会以两个或更多字节进行编码,如下例所示:
(flexi-streams:string-to-octets "wife" :external-format :utf-8)
==> #(119 105 102 101)
(flexi-streams:string-to-octets "????" :external-format :utf-8)
==> #(208 182 208 181 208 189 208 176)
Run Code Online (Sandbox Code Playgroud)
两个字符串具有相同的长度,但俄语单词的每个字符编码为八位字节数的两倍,因此字符串的总大小是英语字符串的两倍.因此,如果使用读序列,知道字符串长度是没有用的.即使字符串的大小(即编码它所需的八位字节的数量)是已知的,仍然无法知道哪些八位字节单独转换为字符形式以及哪些组合在一起进行转换.因此,我试图找到一种方法来实现(Clozure CL)或外部库为我做的工作,而不是滚动我自己的函数.不幸的是,这也存在问题,因为我的解析器依赖于对所有读取函数使用相同的文件流,如下所示:
(with-open-file (stream "test.dat" :direction :input
:element-type '(unsigned-byte 8))
;;Read entire contents of NBT file from stream here)
Run Code Online (Sandbox Code Playgroud)
这限制了我:element-type …
在Common Lisp HyperSpec页面上unread-char- 请参阅
此处 - 它说明了以下两点:
"unread-char旨在成为允许Lisp读者和其他解析器在输入流中执行单字符预测的有效机制."
"在同一个流上连续两次调用unread-char是错误的,而没有在该流上调用read-char(或其他隐式读取字符的输入操作)."
我正在研究如何为我正计划编写的解析器添加对CL流的多字符前瞻支持,并且为了确认上述内容,我运行了以下代码:
(defun unread-char-test (data)
(with-input-from-string (stream data)
(let ((stack nil))
(loop
for c = (read-char stream nil)
while c
do (push c stack))
(loop
for c = (pop stack)
while c
do (unread-char c stream)))
(coerce
(loop
for c = (read-char stream nil)
while c
collect c)
'string)))
(unread-char-test "hello")
==> "hello"
Run Code Online (Sandbox Code Playgroud)
它没有抛出错误(在SBCL或CCL上,我还没有在其他实现上测试它)但我没有看到可能有任何读取操作(隐式或显式)发生在流之间连续打电话给unread-char.
这种行为对于多字符前瞻来说是好消息,只要它是一致的,但为什么不抛出错误?