暂定答案(关于实际问题)
如前所述,无法通过自省命令或调用帧来可靠地检测给定命令 (proc) 的嵌套评估。此外,这需要访问 Tcl 内部(私有头文件等)并且它仅适用于 Tcl 8.6+(如果这对您很重要)。 if-ed 和非if 调用您的命令 (myCommand) 的情况,至少对于您向我们透露的内容,可以通过以下方式检测到:
CmdFrame *framePtr;
Interp *iPtr = ((Interp *)interp);
Tcl_Obj* resObj = Tcl_NewIntObj(1);
framePtr = iPtr->cmdFramePtr;
Tcl_ResetResult(interp);
if (iPtr->cmdFramePtr->nextPtr &&
iPtr->cmdFramePtr->nextPtr->framePtr ==
iPtr->cmdFramePtr->framePtr &&
iPtr->cmdFramePtr->framePtr == iPtr->varFramePtr) {
Tcl_SetObjResult(interp, resObj);
} else {
fprintf(stderr, "The result is %s\n", Tcl_GetString(resObj));
}
return TCL_OK;
然后执行如下脚本:
myCommand; # w/ print-out
set y [myCommand]; # w/ print-out
if {[myCommand]} { puts "then, here!"} else {puts "notok"}; # w/o print-out
但是,您会注意到无法区分以下两种 if-inner 用法:
if {[myCommand]} { puts "then, here! [myCommand]" } else {puts "notok"}
第二个也不会打印出来。只有命令堆栈上的(人为创建的)额外框架会这样做:
if {[myCommand]} { puts "then, here! [apply {{} {myCommand}}]"} else {puts "notok"};
因此,如示例所示,这种方法不能一概而论。
改进建议
“如果命令结果在 [] 括号等内,我不应该打印它。”
我想回到 Donal 的一个建议,并演示如何通过提供两个单独的命令(每个上下文一个命令,同时保留外观)轻松解开命令的不同使用上下文 (myCommand) n'feel 有一个。您可以提供一个计算预期返回值的主要包装器,并提供一个辅助包装器,将返回值“重定向”到标准输出(或其他)。
(1) 主命令:将现有的、C 实现的命令变成不 打印结果值,但仅使用Tcl_SetObjResult 返回结果的命令。将此命令(或将其别名放入)::tcl::mathfunc::* 命名空间。使用Critcl,可能看起来像这样:
critcl::ccommand ::tcl::mathfunc::myCommand {cd interp objc objv} {
Tcl_Obj* resObj = Tcl_NewIntObj(1);
Tcl_SetObjResult(interp, resObj);
return TCL_OK;
}
(2) 在顶层 (::) 或具有相同(非限定)名称 myCommand 的项目特定命名空间中创建脚本化包装器:
proc ::myCommand {} {
puts stdout [uplevel 1 ::tcl::mathfunc::myCommand]
return
}
包装器调用主命令和puts 任何需要的结果。 return 将重置解释器的结果。或者,您也可以将其返回,因此包装器与主命令等效。
您现在可以在[expr] 环境中以不同方式使用myCommand,例如[if] 条件和非expr 条件:
myCommand; # main w/ print-out
if {myCommand()} { puts "then, here" }; # wrapper w/o print-out
set x [myCommand]; # main w/ print-out, x is set to ""
set y [expr {myCommand()}]; # wrapper w/o print-out, y is set to result
所有这一切都依赖于专用命名空间::tcl::mathfunc 以及其中的命令/过程如何由[expr] 处理。对我的好处是:
- 允许直接重构(您的主命令变得更简单)并且包装器完全是脚本化的。
- 使用上下文的清晰分离,甚至在语法上。
- 无需自省命令的
CmdFrame/CallFrame 上下文,这无论如何都会受到限制,并且需要访问 Tcl 内部。
- 对您的命令的每次调用都没有自省惩罚。
原创建议
你在追求某事吗?沿着(脚本)行:
proc myCommand {} {
for {set i 1} {$i<=[info frame]} {incr i} {
set frameInfo [info frame $i]
set frameType [dict get $frameInfo type]
set cmd [dict get $frameInfo cmd]
if {$frameType eq "source" && [lindex $cmd 0] eq "if"} {
puts stderr {Called from (anywhere) within [if]}
break;
}
}
return 1
}
myCommand;
# some ancestor stackframe might reveal some [if] context
if {[myCommand]} { puts "then, here!"};
你可以实现某事。在 C 端类似,通过使用TclGetFrame,获取当前堆栈帧,然后沿着堆栈向下爬。然而,正如 Donal 明确指出的那样,这有一种非常强烈的气味,可能会导致许多误报(例如,if 结构中的任何地方的[myCommand],if 不一定表示所谓的控制流结构,以防有人决定捎带这个命令名称等)
首先,最好重新评估您为什么觉得有必要这样做。
问题是我不知道以后该做什么,因为我无法打印命令
结果,如果它在 [] 括号内等。
例如,我仍然不清楚是否在if-context 中调用它对您的命令的客户端有什么影响。正如多纳尔所写的那样,它不应该有所作为。
如果你想在if-condition 中打印出myCommand 的返回值,而不改变行为,那么写……就像在你的脚本中一样(而不是操纵命令本身):
if {[set tmp [myCommand]; puts $tmp; set tmp]} { puts "then, here!"};
更新
如果您不想操纵脚本(在myCommand 的调用站点),请使用execution trace 来观看您的myCommand 执行:
proc logMyCommand {call code result op} {
# puts ...
}
trace add execution myCommand leave logMyCommand