评估 Common Lisp 宏的参数

2024-05-13

我想制作一个宏,其行为取决于它的参数之一。例如:

(defclass myvar ()
  ((l :initarg :l
      :reader l)))

(defparameter mv1 (make-instance 'myvar :l 10))

(defmacro mac1 (v)
  `(progn
     ,@(loop for x upto (eval `(l ,v)) collect `(format t "~A~%" ,x))))

(mac1 mv1)

1
2
3
4
5
6
7
8
9
10

这很好用。 但如果我尝试对局部变量执行相同的操作:

(let ((mv2 (make-instance 'myvar :l 10)))
  (mac1 mv2))

我收到错误:

 The variable MV2 is unbound.
 It is a local variable not available at compile-time.

有没有办法评估宏中的局部变量?


您的目标是将一些代码编译成另一种语言。 几乎不需要宏来做到这一点,您可以采用结构化表达式并生成另一种树,或者直接将外语代码作为字符串发出。

有点令人困惑的是,你的代码看起来像 Lisp,但实际上并不像 Lisp 那样工作,因为你想要loop-on能够检查其词法环境以了解某些符号的编译时绑定。 标准中没有指定这一点,即使您可以在实现中找到一种方法来访问它,例如使用&environment宏中的关键字,那么该解决方案将不可移植。

无论如何,您都必须指定如何将 Lisp 形式转换为 C,这意味着您必须实现一个解释器(就其一般含义而言,即对代码进行解释)。

如果您有简单的要求,一种简单的方法是make-var and loop-on数据的构造函数。

让我们定义一下var and loop-on结构:

 (defstruct (var (:constructor make-var (x y))) x y)
 (defstruct loop-on var code)

结构简单

因为我还不知道你想如何利用body,让我们按原样存储它。 该部分需要一个宏,但是在您指定如何编译主体之前,这不会很有用。

(defmacro loop-on (v &body body)
  `(make-loop-on :var ,v :code ',body))

现在,您的代码可以被评估为 Lisp 代码:

(let ((v1 (make-var 100 100)))
  (loop-on v1 (some-code-here)))

结果值为:

#S(LOOP-ON :VAR #S(VAR :X 100 :Y 100) :CODE ((SOME-CODE-HERE)))

现在,您的编译器可以将此循环扩展为 C 代码,前提是:CODEslot 满足你想要支持的 Lisp 子集。

通用解释器

对于较大的项目,您需要定义一个知道如何解释代码的代码遍历器。 我们定义 6 个通用函数,如下:

(defgeneric interpret-let (interpreter env bindings code))
(defgeneric interpret-body (interpreter env forms))
(defgeneric interpret-loop (interpreter env var forms))
(defgeneric interpert-lisp (interpreter env form))

(defgeneric interpret (interpreter env code)
  (:method (i env code)
    (optima:ematch code
      ((list* 'let bindings code)
       (interpret-let i env bindings code))
      ((list 'make-var x y)
       (make-var x y))
      ((list* 'loop-on v code)
       (interpret-loop i env v code))
      ((list 'lisp form)
       (interpret-lisp i env form)))))

代码取决于optima用于模式匹配。输入代码与已知语法进行匹配,并将行为委托给通用函数,这些函数根据您正在构建的解释器的类型进行调度;

Then env环境变量是一个词法环境,它可以保存变量名称、函数、类型等的绑定。您可以在env也是如此,但这里我们假设这是一个从符号到值的关联列表。 以下是如何增强一组给定绑定的环境,其中每个绑定都是将符号映射到表单的列表:

(defgeneric augment-env (interpreter env bindings)
  (:method (i env bindings)
    (nconc (loop for (n v) in bindings
                 collect (cons n (interpret i env v)))
           env)))

评估员

在这里,我专门介绍了名为的解释器的方法:eval。 请注意,我们不是针对类进行匹配,而是针对关键字进行匹配。 对于更复杂的情况,您可以在解释器中存储一些状态。

(defmethod interpret-body ((i (eql :eval)) e (form cons))
  (destructuring-bind (form . rest) form
    (cond
      (rest (interpret i e form)
            (interpret-body i e rest))
      (t (interpret i e form)))))

(defmethod interpret-let ((i (eql :eval)) env bindings code)
  (interpret-body i (augment-env i env bindings) code))

(defmethod interpret-lisp ((i (eql :eval)) env form)
  (eval `(let ,(loop for (a . b) in env collect (list a b))
           (declare (ignorable ,@(mapcar #'car env)))
           ,form)))

(defmethod interpret-loop ((i (eql :eval)) e v body)
  (let ((var (cdr (assoc v e))))
    (dotimes (ii (var-x var))
      (dotimes (jj (var-y var))
        (interpret-body i
                        (acons 'i ii (acons 'j jj e))
                        body)))))

通过上述定义,您可以将代码解释如下:

(interpret :eval
           nil
           '(let ((v1 (make-var 10 5)))
             (loop-on v1 (lisp (print (list i j))))))

这将执行双循环并将值打印到标准输出。

代码扩展器

现在让我们编写一个代码解释器,将输入形式扩展为另一种语言。 实际上,如果目标语言是 C,我会在这里编写代表 C 代码的代码。 然后生成的代码可以像 C 一样漂亮地打印(这往往比直接编写 C 更好)。

(defmethod interpret-let ((i (eql :expand)) e bindings code)
  (loop for (a b) in bindings
        for v = (interpret i e b)
        if (typep v 'var)
        collect (cons a v) into new-env
        else
        collect (list a v) into new-bindings
        finally
           (return
             `(c/block
                ;; remove VAR instances (they are expanded at compile-time)
                (c/declare ,new-bindings)
                ,@(interpret-body i (append new-env e) code)))))
    
(defmethod interpret-body ((i (eql :expand)) e code)
  (loop for f in code collect (interpret i e f)))

(defmethod interpret-loop ((i (eql :expand)) e v body)
  (let ((var (cdr (assoc v e))))
    (assert var)
    `(c/for (i (< i ,(var-x var)) (++ i))
       (c/for (j (< j ,(var-y var)) (++ j))
            ,@(interpret-body i
                              (augment-env i e `((i i) (j j)))
                              body)))))

(defmethod interpret ((i (eql :expand)) e (code symbol))
  code)

调用 Lisp 的最后一部分对于您的情况可能并不重要,但假设我们有一种方法可以在 C 代码中调用 Lisp 解释器,该解释器可以将变量绑定列表作为参数。

(defmethod interpret-lisp ((i (eql :expand)) e form)
  `(c/lisp-lexenv-eval ,(princ-to-string form)
                       ,(loop for (a . b) in e
                              when (symbolp b)
                              append (list (string a) b))))

使用这个扩展器,结果会有所不同:

(interpret :expand
           nil
           '(let ((v1 (make-var 10 5)))
             (loop-on v1 (lisp (print (list i j))))))

生成的代码是:

(C/BLOCK (C/DECLARE NIL)
  (C/FOR (I (< I 10) (++ I))
         (C/FOR (J (< J 5) (++ J))
                (C/LISP-LEXENV-EVAL "(PRINT (LIST I J))"
                                    ("I" I "J" J)))))

有了漂亮的打印机,您就可以发出相应的 C 代码。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

评估 Common Lisp 宏的参数 的相关文章

  • SBCL:将 Hunchentoot 应用程序部署为可执行文件

    我开始使用 SBCL Common Lisp 并希望使用 Hunchentoot 开发一个小型 Web 应用程序 为了便于部署 我计划使用 sb ext save lisp and die 将所有内容保存在二进制文件中 因为我可以忍受大输出
  • common lisp:宏如何使用以编程方式生成的名称定义其他方法/宏?

    我意识到我的代码的某个部分由看起来相似的方法组组成 就像我有多个三重奏 一个由程序员的其他两个函数调用的辅助函数 我正在尝试编写一个宏来为我定义这三个函数 以便我所需要做的就是调用该宏 但我的尝试导致 defun 和函数调用将引用字符串而不
  • LISP:关键字参数,提供-p

    目前我正在学习 Peter Seibel 的 Practical Common Lisp 在 实用 一个简单的数据库 一章中 http www gigamonkeys com book practical a simple database
  • Common Lisp 中的未绑定变量

    我是 Lisp 新手 正在阅读 ANSI Common Lisp 第 8 章中的文本生成器示例 我按照该示例并在 LET 变量 prec 的范围内定义了一个函数 see let prec defun see symb let pair as
  • Lisp 内部引用工作

    口齿不清是如何做到的quote内部工作 例如 quote 1 1 2 似乎相当于 list 1 list 1 2 这意味着它是如何递归地象征 Head 值的 这个功能是内置的吗 Run equal quote 1 1 2 list 1 li
  • Common Lisp 中的 LET 与 LET*

    我理解 LET 和 LET 并行绑定与顺序绑定 之间的区别 并且作为理论上的问题 它非常有意义 但有没有什么情况你曾经真正需要过 LET 在我最近查看的所有 Lisp 代码中 您可以将每个 LET 替换为 LET 而无需进行任何更改 编辑
  • 在 Parenscript 中使用 regex(正则表达式)

    我正在尝试 Parenscript 在尝试使用正则表达式函数时 我得到了意外的输出 例如 参考手册 https common lisp net project parenscript reference html shows regex f
  • 如何定义和使用 Common Lisp 包(库)?

    我在几个 Lisp 源文件中开发了一些 Common Lisp 函数 我希望这些函数可以轻松地供我编写的其他函数使用 或者如果我认为它们对其他人有用的话 可以在 github 上提供 现在 我只是将它们放在一些预定义的文件夹中并使用 req
  • 我应该在 Common Lisp 中使用哪些正则表达式库? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 任意类型说明符上的 Defmethod?

    我想做的是 defgeneric fn x defmethod fn x integer 1 Positive integer defmethod fn x integer 1 Negative integer 我想要一个可以与任意类型说明
  • 用通用函数替换普通函数

    我想将 elt nth 和 mapcar 等名称与我正在原型设计的新数据结构一起使用 但这些名称指定普通函数 因此我认为需要将其重新定义为通用函数 重新定义这些名称可能是一种不好的形式 有没有办法告诉 defgeneric 不要生成程序错误
  • 在Emacs中,这个错误是什么意思? “警告:运行时需要 cl 包”

    我正在字节编译一个模块 它给了我这个警告 Warning cl package required at runtime 为什么这是一个警告 我很清楚我正在使用cl包裹 事实上有一个 require cl 模块中的语句 使用有什么问题吗cl
  • 宏、Clojure 与 Common Lisp

    我和我的一些朋友正在开发一个新平台 我们想用 lisp 构建它 主要吸引力是宏 我们都使用 Common Lisp 但我想探索 Clojure 的选择 当我提出这一点时 其中一位说宏观体系 较弱 我想知道这是否属实 以及在哪些领域 就您可以
  • 评估 Common Lisp 宏的参数

    我想制作一个宏 其行为取决于它的参数之一 例如 defclass myvar l initarg l reader l defparameter mv1 make instance myvar l 10 defmacro mac1 v pr
  • 从 CCL 检索(加载)源代码?

    我打了电话 load code lisp 用CCL 然后不小心删除了code lisp 有什么办法可以找回源代码吗 CCL 在内存中是否有它 这是一个非常特殊的功能 这里只为克洛祖尔CL 该代码在其他地方不起作用 这在 CCL IDE 中对
  • 学习 LISP 的最佳方法是什么? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • Common Lisp 相当于 format 函数中的 \r 吗?

    基本上 我想只使用 Common Lisp 而不是 Python 来执行以下操作 print Hello world r n 我可以做到这一点 但它只输出 newline字符和跳过 return format t Hello world 我
  • Lisp:使用语法糖访问递归哈希

    我正在尝试构建一个函数 或宏 来简化哈希表深处数据的获取和设置 也就是说 哈希中的哈希 哈希中的哈希等 我不认为我可以用宏来做到这一点 而且我不知道如何用 eval 来做到这一点 我希望能够执行以下操作 gethashdeep HEROES
  • Scheme 和 Common Lisp 之间的实际区别是什么? (或任何其他两种 Lisp 方言)

    注意 我并不是在问学哪个 哪个更好或者诸如此类的问题 我选择了 SICP 的免费版本 因为我觉得它读起来很好 我听说过有关它的好东西 并且我对编程的这一方面很感兴趣 我知道Scheme 是Lisp 的一种方言 我想知道 Scheme 和Co
  • defvar、defparameter、setf 和 setq 之间有什么区别

    我找到了一个类似问题 https stackoverflow com questions 3855862 setq and defvar in lisp 但我不太明白这个解释 所以我尝试使用以下示例运行 clisp 1 gt defvar

随机推荐