Tcl 在8.4中运行的代码导致了8.6中的崩溃,是否有更好的方法实现该功能

Tcl 在8.4中运行的代码导致了8.6中的崩溃,是否有更好的方法实现该功能,tcl,Tcl,我有一个Tcl实用程序,可以轻松确保在控制流离开过程的当前范围时运行代码片段。它在TCL8.6.6中崩溃,所以我想知道是否有更好的方法来实现TCL8.6中的功能 一个示例用法是: proc test {file} { set fh [open $file] ::Util::Defer [list close $fh] # ... do a bunch of stuff # and even if we hit an error # [close $fh] will

我有一个Tcl实用程序,可以轻松确保在控制流离开过程的当前范围时运行代码片段。它在TCL8.6.6中崩溃,所以我想知道是否有更好的方法来实现TCL8.6中的功能

一个示例用法是:

proc test {file} {
   set fh [open $file]
   ::Util::Defer [list close $fh]
   # ... do a bunch of stuff
   # and even if we hit an error
   # [close $fh] will be evaluated as we return
   # from the proc
}
它在TCL8.4中工作得非常好,我在代码中都使用它

由于我仍在学习Tcl 8.6中的所有可用功能,我想问的是,应该如何编写::Util::Defer proc以最好地利用Tcl 8.6

以下是8.4的实现:

namespace eval ::Util {}
proc ::Util::Defer_impl {cmd args} {
  uplevel 1 $cmd
}
proc ::Util::Defer {cmd} {
  set vname _u_defer_var
  # look for a unique variable name
  while {[uplevel 1 [list info vars $vname]] != ""} {
    set vname ${vname}_
  }
  uplevel 1 [list set $vname $cmd]
  # when the variable is unset, trigger a call to the command
  uplevel 1 [list trace add variable $vname unset [list ::Util::Defer_impl $cmd]]
  # return a chunk of code enabling the user to cancel this if desired
  return [list variable $vname unset [list ::Util::Defer_impl $cmd]]
}
编辑以添加: 我很感激你的回答。老实说,我已经为文件句柄提供了其他语法糖,如下所示:

proc test {file} {
   set fh [::Util::LocalFileHandle $file]
   # do stuff
}
我只是希望得到一个针对::Util::Defer的通用解决方案——因为我偶尔会在同一个proc中的不同位置使用两到三个。是的,如果不存在或不可读,我将省略错误处理

注意:我已经向ActiveState报告了这个bug,并提交了一份报告

编辑以添加错误代码:这是导致我崩溃的Tcl代码,它稍微精简到了本质,而不是完整的::Util::Defer


未经测试的代码正是这段代码,我已经多次使用此模式:

proc test file {
    try {
        open $file
    } on ok fh {
        # do stuff with fh
        # more stuff 
    } finally {
        catch {close $fh}
    }
}
应该差不多。无论是否使用try结构处理错误,或者是否出现错误,finally子句中的代码在结束时都会运行。如果希望能够取消操作,请在子句中使用简单的If

编辑

如果希望看到通道关闭时生成的任何错误,最好将其包装在catch中,如果无法打开文件且未创建通道id变量,则必须使用catch。备选方案包括:

检查是否存在:如果{[info exists fh]}{close$fh} 传播关闭错误:使用result和options变量名参数捕获。
您描述的模式是受支持的模式;它不应该崩溃,事实上,我不能用8.6.3或8.6支持分支的尖端重现崩溃。它唯一的问题是,如果在关闭或任何其他延迟脚本期间出现错误,它将不会报告错误,正如您从该代码段中看到的那样%is提示符:

% apply {{} {
    ::Util::Defer [list error boo]
    puts hi
}}
hi
% 
这就是为什么我花了很多精力在8.6中提供try命令的部分原因。有了它,您可以执行以下操作:

proc test {filename} {
    set f [open $filename]
    try {
        # Do stuff with $f
    } finally {
        close $f
    }
}
它还处理一些棘手的事情,比如将正文和finally子句中抛出的错误缝合在一起。正文异常信息位于finally子句的error exception info的-during选项中,这样,如果两个地方都有错误,您就可以同时找到这两个地方

% catch {
    try {
        error a
    } finally {
        error b
    }
} x y
1
% puts $x
b
% puts $y
-errorstack {INNER {returnImm b {}}} -errorcode NONE -errorinfo {b
    while executing
"error b"} -errorline 5 -during {-code 1 -level 0 -errorstack {INNER {returnImm a {}}} -errorcode NONE -errorinfo {a
    while executing
"error a"} -errorline 3} -code 1 -level 0
就我个人而言,我更倾向于这样写:

proc withreadfile {varName filename body} {
    upvar 1 $varName f
    set f [open $filename]
    try {
        return [uplevel 1 $body]
    } finally {
        close $f
    }
}

proc test {file} {
    withreadfile fh $file {
        # Do stuff with $fh
    }
}

您的里程数可能会有所不同。

周末,这个重量级解决方案浮现在脑海中。它利用itcl::local功能实现相同的效果。它确实依赖于Itcl,但由于问题是与Itcl的交互,因此这似乎是一个合理的解决方案,尽管它不是纯粹的Tcl

itcl::class Defer_impl {
  constructor {cmd} {} {
    set _to_eval $cmd
  }
  destructor {
    uplevel 1 $_to_eval
  }
  private variable _to_eval {}
}

proc ::Util::Defer {cmd} {
  uplevel 1 [list itcl::local ::Defer_impl #auto $cmd]
}

在Ubuntu16.04下,TCL8.6.0似乎没有这样的问题。我没有8.6.0,只有8.6.4和8.6.6,对于Red Hat 6.6.Hmm,我会使用set vname[expr rand]作为延迟变量的初始猜测。如果打开失败,代码将尝试关闭未设置的变量。您可以通过捕获来处理这一问题,这将丢失关闭管道时的错误…罪名成立,大人。谢谢,我喜欢新的try/finally,并将开始使用它,但我希望能够有一点替代::Util::Defer的语法糖。这可能是我们得到的构建中的一个bug,或者是某个环境中的bug,或者。。。我不知道。我可能需要自己构建Tcl/Tk/IncrTcl,看看是否还能复制这个。使用提供的调试库和优化后的脚本,我肯定会得到一个空指针和一个seg错误…@TreyJackson IncrTcl?这可能与问题有关。它将触角深深地伸入Tcl,可能会让事情陷入一种奇怪的状态。你所做的应该是有效的;它至少支持到8.*系列结束。我为9.0计划了一些不同的东西——更多地关注堆栈框架消失的用例——因为局部变量跟踪对于高级编译来说是有问题的。我相信它与IncrTcl密切相关,因为它只在我从IncrTcl类中的proc而不是方法中使用时失败。我将更新我的答案,以获得错误报告中的代码,从而明确说明什么不起作用。这是一个增量错误,dgp已在4.0.6中修复了它。
itcl::class Defer_impl {
  constructor {cmd} {} {
    set _to_eval $cmd
  }
  destructor {
    uplevel 1 $_to_eval
  }
  private variable _to_eval {}
}

proc ::Util::Defer {cmd} {
  uplevel 1 [list itcl::local ::Defer_impl #auto $cmd]
}