在常见的 lisp 包中导出照应宏
Exporting anaphoric macros in common lisp packages
我在导出宏时遇到问题,它在同一包中声明时有效,但在导入时无效。我在 Windows.
上使用 Emacs、SLIME、Clozure
打包文件
(defpackage :tokenizer
(:use :common-lisp)
(:export :tokenize-with-symbols
:current-token
:advanze-token
:peek-token
:with-token-and-peek
:with-token))
(defpackage :csharp-parser
(:use :common-lisp :tokenizer)
(:import-from :tokenizer :with-token-and-peek :with-token))
分词器文件
(in-package :tokenizer)
(defmacro with-token-and-peek (&body body)
`(let ((token (current-token tokenizer))
(peek (peek-token tokenizer)))
,@body))
解析器文件
(in-package :csharp-parser)
(defun expression (tokenizer node-stack)
(with-token-and-peek
(cond ((is-number? token) (make-value-node "number" token))
((is-bool? token) (make-value-node "bool" token))
((is-identifier? token peek) (make-identifier-node tokenizer node-stack))
(t (make-ast-node :identifier "bla")))))
给出编译错误:
csharpParser.lisp:265:3:
warning: Undeclared free variable TOKENIZER::TOKENIZER (2 references)
style-warning: Unused lexical variable TOKENIZER::PEEK
style-warning: Unused lexical variable TOKENIZER::TOKEN
csharpParser.lisp:266:14:
warning: Undeclared free variable TOKEN
etc etc etc
如果我在包中尝试宏扩展:csharp-parser
(macroexpand-1 '(with-token-and-peek tok))
(LET ((TOKENIZER::TOKEN (CURRENT-TOKEN TOKENIZER::TOKENIZER))
(TOKENIZER::PEEK (PEEK-TOKEN TOKENIZER::TOKENIZER)))
TOK)
T
现在就像我说的那样,如果我将宏移动到解析器文件,它会编译并完美运行。但是当我尝试将它重构为 tokenizer 文件并通过包系统导出它时,它会出现这些错误,因为它似乎将符号内部化到调用包中。我通过冒号尝试了多种方法,但无法正常工作。
如果有人能帮我解决这个问题,我将不胜感激。
宏中的符号 TOKEN
和 PEEK
驻留在 TOKENIZER
包中,而 COND
中的代码使用 [=17] 中的符号=] 包。有两种解决方法。
让扩展使用驻留在代码所在包中的符号。这可以通过在展开宏时手动将符号驻留在当前包中来完成。例如:
(defpackage #:foo
(:use #:cl)
(:export #:aif))
(in-package #:foo)
(defmacro aif (test then &optional else)
(let ((it (intern (symbol-name 'it))))
`(let ((,it ,test))
(if ,it ,then ,else))))
(in-package :cl-user)
(use-package :foo)
(aif (+ 3 3) it) ;=> 6
使用 (intern (symbol-name 'it))
而不是 (intern "IT")
是一种避免 lisp 不将符号转换为大写的问题的方法。
让代码使用分词器包中内置的符号。这可以通过导出符号来完成。
(defpackage #:foo
(:use #:cl)
(:export #:aif
#:it))
(in-package #:foo)
(defmacro aif (test then &optional else)
`(let ((it ,test))
(if it ,then ,else)))
(in-package :cl-user)
(use-package :foo)
(aif (+ 3 3) it) ;=> 6
缺点是宏的使用者必须导入符号,所以他们不能使用宏的包限定名。
(defpackage #:foo
(:use #:cl)
(:export #:aif
#:it))
(in-package #:foo)
(defmacro aif (test then &optional else)
`(let ((it ,test))
(if it ,then ,else)))
(in-package :cl-user)
(foo:aif (+ 3 3) it) ; Fails
发生这种情况是因为宏 with-interned-symbols 扩展为包含在 TOKENIZER 中驻留的符号的代码,而 cond 表达式仅包含在 CSHARP-PARSER 中驻留的符号。宏扩展包含的任何符号(关键字或 gensyms 除外)都应保留。
以下宏会将符号列表存入同名变量:
(defmacro with-interned-symbols (symbol-list &body body)
"Interns a set of symbols in the current package to variables of the same (symbol-name)."
(let ((symbol-list (mapcar (lambda (s)
(list s `(intern (symbol-name ',s))))
symbol-list)))
`(let ,symbol-list ,@body)))
宏 with-token-and-peek 可以使用上面的方式重新定义以避免这种不匹配:
(with-interned-symbols (token peek)
(defmacro with-token-and-peek (&body body)
`(let ((,token (current-token tokenizer))
(,peek (peek-token tokenizer)))
,@body)))
请注意,虽然照应宏可能是这里最明显的特例,但这种情况可能发生在任何将新符号引入扩展的宏中,但关键字除外,因为它们始终位于关键字包中。
我在导出宏时遇到问题,它在同一包中声明时有效,但在导入时无效。我在 Windows.
上使用 Emacs、SLIME、Clozure打包文件
(defpackage :tokenizer
(:use :common-lisp)
(:export :tokenize-with-symbols
:current-token
:advanze-token
:peek-token
:with-token-and-peek
:with-token))
(defpackage :csharp-parser
(:use :common-lisp :tokenizer)
(:import-from :tokenizer :with-token-and-peek :with-token))
分词器文件
(in-package :tokenizer)
(defmacro with-token-and-peek (&body body)
`(let ((token (current-token tokenizer))
(peek (peek-token tokenizer)))
,@body))
解析器文件
(in-package :csharp-parser)
(defun expression (tokenizer node-stack)
(with-token-and-peek
(cond ((is-number? token) (make-value-node "number" token))
((is-bool? token) (make-value-node "bool" token))
((is-identifier? token peek) (make-identifier-node tokenizer node-stack))
(t (make-ast-node :identifier "bla")))))
给出编译错误:
csharpParser.lisp:265:3:
warning: Undeclared free variable TOKENIZER::TOKENIZER (2 references)
style-warning: Unused lexical variable TOKENIZER::PEEK
style-warning: Unused lexical variable TOKENIZER::TOKEN
csharpParser.lisp:266:14:
warning: Undeclared free variable TOKEN
etc etc etc
如果我在包中尝试宏扩展:csharp-parser
(macroexpand-1 '(with-token-and-peek tok))
(LET ((TOKENIZER::TOKEN (CURRENT-TOKEN TOKENIZER::TOKENIZER))
(TOKENIZER::PEEK (PEEK-TOKEN TOKENIZER::TOKENIZER)))
TOK)
T
现在就像我说的那样,如果我将宏移动到解析器文件,它会编译并完美运行。但是当我尝试将它重构为 tokenizer 文件并通过包系统导出它时,它会出现这些错误,因为它似乎将符号内部化到调用包中。我通过冒号尝试了多种方法,但无法正常工作。
如果有人能帮我解决这个问题,我将不胜感激。
宏中的符号 TOKEN
和 PEEK
驻留在 TOKENIZER
包中,而 COND
中的代码使用 [=17] 中的符号=] 包。有两种解决方法。
让扩展使用驻留在代码所在包中的符号。这可以通过在展开宏时手动将符号驻留在当前包中来完成。例如:
(defpackage #:foo (:use #:cl) (:export #:aif)) (in-package #:foo) (defmacro aif (test then &optional else) (let ((it (intern (symbol-name 'it)))) `(let ((,it ,test)) (if ,it ,then ,else)))) (in-package :cl-user) (use-package :foo) (aif (+ 3 3) it) ;=> 6
使用
(intern (symbol-name 'it))
而不是(intern "IT")
是一种避免 lisp 不将符号转换为大写的问题的方法。让代码使用分词器包中内置的符号。这可以通过导出符号来完成。
(defpackage #:foo (:use #:cl) (:export #:aif #:it)) (in-package #:foo) (defmacro aif (test then &optional else) `(let ((it ,test)) (if it ,then ,else))) (in-package :cl-user) (use-package :foo) (aif (+ 3 3) it) ;=> 6
缺点是宏的使用者必须导入符号,所以他们不能使用宏的包限定名。
(defpackage #:foo (:use #:cl) (:export #:aif #:it)) (in-package #:foo) (defmacro aif (test then &optional else) `(let ((it ,test)) (if it ,then ,else))) (in-package :cl-user) (foo:aif (+ 3 3) it) ; Fails
发生这种情况是因为宏 with-interned-symbols 扩展为包含在 TOKENIZER 中驻留的符号的代码,而 cond 表达式仅包含在 CSHARP-PARSER 中驻留的符号。宏扩展包含的任何符号(关键字或 gensyms 除外)都应保留。
以下宏会将符号列表存入同名变量:
(defmacro with-interned-symbols (symbol-list &body body)
"Interns a set of symbols in the current package to variables of the same (symbol-name)."
(let ((symbol-list (mapcar (lambda (s)
(list s `(intern (symbol-name ',s))))
symbol-list)))
`(let ,symbol-list ,@body)))
宏 with-token-and-peek 可以使用上面的方式重新定义以避免这种不匹配:
(with-interned-symbols (token peek)
(defmacro with-token-and-peek (&body body)
`(let ((,token (current-token tokenizer))
(,peek (peek-token tokenizer)))
,@body)))
请注意,虽然照应宏可能是这里最明显的特例,但这种情况可能发生在任何将新符号引入扩展的宏中,但关键字除外,因为它们始终位于关键字包中。