如何在Common Lisp中实现`tagbody`和`go`?

Sam*_*urn 5 compiler-construction common-lisp

如何tagbodygoCommon Lisp中实现的?它是某种形式的setjmp/longjmp还是有更优雅的方式来处理它?

我正在编写一个用C实现的lispy语言,并希望有类似的东西.

BRP*_*ock 5

从实现的角度来看,如果你正在解释一个类似 Lisp 的程序,你可能会做一些这样的事情:

  • 输入 a 后tagbody,开始一个目的地表。(符号映射?地址对)
  • 迭代每个表单中的 tagbody
  • if (symbolp this-element),然后将地址(指向该表单的指针)存储到表中
  • 否则,(eval this-element)像往常一样
  • 遇到go表单时,查找目标符号,并(破坏性地)将程序的“当前指令”指针更改为该值。然后,跳转到您的例程以获取下一条指令。
  • 退出时tagbody,只需丢弃目标表。

目标表(最终)需要是一个堆栈(在较早的 Lisp 文档中称为“下推列表”或 PDL),因为您将在动态范围中向上搜索以找到有问题的标签。请记住,在 Common Lisp 中,go标签是一个独立于变量、函数、类等的命名空间。

@jlahd 是正确的,它实际上与 C 中的 (limited-range) 相同goto,但是如果您正在解释代码,您实际上会用存储的值覆盖“程序计数器”指针。


ace*_*ent 3

将 Common Lisp 简化go为其他语言goto实在是过于简化了。

在 Common Lisp 中,go可以展开堆栈。例如:

(tagbody
    (mapc #'(lambda (el1 el2)
              (format t "el1: ~a, el2: ~a~%" el1 el2)
              (when (or (null el1) (null el2))
                (go stop)))
          list1
          list2)
  stop)
Run Code Online (Sandbox Code Playgroud)

如果您用 C 实现 Common Lisp,那么非展开go可能是常规goto,但展开go需要setjmp/longjmp或等效功能,并具有适当的堆栈展开,如果需要,则后跟常规goto,即在标记 Lisp 形式的情况下不是 后面的 C 语句或表达式setjmp

如果您有时间抽象它,您可能会想要使用操作系统的异常处理。如果您以后想要与其他语言的功能(例如 C++ 异常)集成,并且该平台可能已经有一个处理程序堆栈,从而unwind-protect自动运行清理表单直到某个堆栈帧,那么它可能会获得更好的回报。

如果您想以最小的努力保持它的可移植性,您可以管理一个线程本地上下文堆栈setjmp,在其中您longjmp可以使用足够的信息来获取最新的上下文,以保持longjmp正确的上下文,并unwind-protect始终运行清理表单。这样,您可能仍然想使用平台的异常处理功能,但仅用于设置来自/到外部调用的展开帧。