Common Lisp中如何实现tagbody
和go
?它是某种形式的setjmp / longjmp还是有一种更优雅的方式来处理它?</ p>
我正在写一个用C实现的lispy语言,并希望有类似的东西。
答案 0 :(得分:3)
从实现的角度来看,如果你解释类似Lisp的程序,你可能会做一些类似的事情:
tagbody
后,开始一个目的地表。 (符号地图→地址对)tagbody
if (symbolp this-element)
,然后将地址(指向该表单的指针)存储到表(eval this-element)
像往常一样go
表单时,查找目标符号,并(破坏性地)将程序的“当前指令”指针更改为该值。然后,跳转到您的例程以获取下一条指令。tagbody
时,只需丢弃目标表。目标表(最终)需要是一个堆栈(在旧的Lisp文档中称为“下推列表”或PDL),因为您将向上搜索动态范围以查找相关标签。请记住,在Common Lisp中,go
标记是变量,函数,类等的独立命名空间。
@jlahd是正确的,它实际上与C中的(有限范围)goto
相同,但是如果你正在解释代码,你实际上将用存储的方法覆盖“程序计数器”指针值。
答案 1 :(得分: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)
如果你用C语言实现Common Lisp,那么非展开go
可能是常规goto
,但展开go
需要setjmp
/ longjmp
或等效功能,具有正确的堆栈展开,如果需要则后跟常规goto
,即如果标记的Lisp表单不是setjmp
之后的C语句或表达式。
如果你有足够的时间抽象它,你可能想要使用操作系统的异常处理。如果您以后想要与其他语言的功能(例如C ++异常)集成,并且平台可能已经有一堆处理程序,那么它可能会得到更好的回报,从而自动运行unwind-protect
清理表单直到某个堆栈框架
如果您希望以最少的工作量保持便携性,则可以管理setjmp
上下文的线程局部堆栈,其中longjmp
到最近的上下文,并保留足够的信息{{1}正确运行longjmp
清理表格。这样,您可能仍然希望使用平台的异常处理功能,但只能设置从/向外部调用展开帧。