在常见的lisp包中导出照应宏

Hei*_*ian 4 macros symbols common-lisp package

我在导出宏时遇到问题,当它在同一个包中声明时它会起作用,但在导入它时却不行.我在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))
Run Code Online (Sandbox Code Playgroud)

Tokenizer文件

(in-package :tokenizer)

(defmacro with-token-and-peek (&body body) 
  `(let ((token (current-token tokenizer))
     (peek (peek-token tokenizer)))
     ,@body))
Run Code Online (Sandbox Code Playgroud)

解析器文件

(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")))))
Run Code Online (Sandbox Code Playgroud)

在编译时给出错误:

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
Run Code Online (Sandbox Code Playgroud)

如果我在包中尝试宏扩展:csharp-parser

(macroexpand-1 '(with-token-and-peek tok))

(LET ((TOKENIZER::TOKEN (CURRENT-TOKEN TOKENIZER::TOKENIZER))
      (TOKENIZER::PEEK (PEEK-TOKEN TOKENIZER::TOKENIZER)))
  TOK)
T
Run Code Online (Sandbox Code Playgroud)

就像我说的,如果我将宏移动到解析器文件,它编译并完美地工作.但是当我尝试将它重构为tokenizer文件并通过包系统导出它时会产生这些错误,因为它似乎将符号内化到调用包中.我已经通过冒号尝试了多种方法,但无法使其发挥作用.

如果有人能帮助我,我会非常感激.

jki*_*ski 6

符号TOKENPEEK宏中的符号被TOKENIZER封装在包中,而COND使用符号内的代码则CSHARP-PARSER封装在包中.有两种方法可以解决这个问题.

  1. 扩展使用代码所在的包中的符号.这可以通过在扩展宏的同时手动实现当前包中的符号来完成.例如:

    (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
    
    Run Code Online (Sandbox Code Playgroud)

    在lisp不将符号转换为大写的情况下,使用(intern (symbol-name 'it))而不仅仅是(intern "IT")避免问题的方法.

  2. 让代码使用tokenizer包中的interned符号.这可以通过导出符号来完成.

    (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
    
    Run Code Online (Sandbox Code Playgroud)

    缺点是宏的用户必须导入符号,因此他们不能使用宏的包限定名称.

    (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
    
    Run Code Online (Sandbox Code Playgroud)