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

How are `tagbody` and `go` implemented under the hood in Common Lisp?

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

我正在编写一种用 C 语言实现的 lispy 语言,我想要这样的东西。

从实现的角度来看,如果您要解释 一个类似 Lisp 的程序,您可能会这样做:

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

目标 tables 将(最终)需要是一个堆栈(在旧的 Lisp 文档中称为 "push-down list" 或 PDL),因为您将通过动态范围向上搜索找到有问题的标签。请记住,在 Common Lisp 中,go 标签是独立于变量、函数、类 等的命名空间。

@jlahd 是正确的,它实际上与 C 中的(有限范围)goto 相同,但如果您正在解释代码,您实际上会覆盖 "program counter" 指针与储值。

将 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)

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

您可能希望使用操作系统的异常处理,如果您能负担得起抽象它的时间。如果您以后想要与其他语言的功能(例如 C++ 异常)集成,并且该平台可能已经有一堆处理程序,因此 运行 unwind-protect 自动清理表单可能会更好某个堆栈框架。

如果您想以最小的努力保持它的可移植性,您可以管理 setjmp 上下文的线程本地堆栈,您可以在其中 longjmp 到具有足够信息的最新上下文以保持 longjmp根据正确的上下文,运行 unwind-protect 清理整个过程。这样,您可能仍想使用平台的异常处理功能,但仅用于设置展开框架 from/to 外部调用。