嵌入式 Tcl:如何检测表达式的评估?

Embedded Tcl: How to detect the evaluation of expression?

我们有一个 Tcl 内置 我们的 C/C++ 应用程序,我们的代码中有一个地方我们必须检查命令是否是 nested 这意味着命令结果稍后设置为:“::Tcl_SetResult(...)”。如果不是,它将打印到控制台或使用“printMessage(...)”将其重定向到文件。

不幸的是,::Tcl_GetCommandInfo 之类的内容并没有提供太多信息。 (我不得不承认我对 Tcl 一无所知)。

示例:(函数的样子,Tcl 调用它,然后我们处理数据,它返回给 Tcl,Tcl 决定它是否是我们的命令并继续):

void traceProc(ClientData clientData, Tcl_Interp pInterp, int nLevel, char* pszCommand, Tcl_CmdProc* pCmdProc, ClientData cmdCliendData, int argc, char* argv[])*

执行时可以看到问题 our_command:

our_command; # -> nLevel == 1
if {[our_command] eq "sth"} {do_sth}; # -> also nLevel == 1

现在,我希望 nLevel 为 2,因为它在 if 语句内或第一个为 0 或有关当前执行命令的某种附加信息。难道我做错了什么?问题是我不知道以后要做什么,因为我 "shouldn't" 打印命令结果,如果它在 [] 括号内等等

Tcl_SetResult函数用于将解释器的结果字段设置为给定的字符串值。 (该字段在所有最新版本的 Tcl 中完全是内部的,实际上是多个真实字段,以便处理内存管理等。这些是您可以忽略的细节。)如果一个命令没有设置结果,它的结果 从 Tcl 的角度来看 将是空字符串。您不需要出于任何其他原因设置结果,尽管结果字段的解释会有所不同:如果命令产生错误(通过其实现函数返回 TCL_ERROR 而不是 TCL_OK),则结果字段包含错误 消息 。调用命令时,结果字段的值是(观察上等同于)一个空字符串。

没有通用的方法可以从 API 中判断命令产生什么:它只产生一个值(逻辑上是一个字符串,因为它是 Tcl 理解的所有其他类型值的逻辑超类型).命令的文档(如果存在)可能更具体。所有 Tcl 的内置命令都应该清楚它们产生什么,并且如果不是在它们被记录为产生成功结果的情况下,应该可以预见地产生错误消息。非 Tcl 本身提供的命令不受此限制;我们不能强迫您正确使用 API!


根据命令的调用上下文改变命令的行为被认为是非常糟糕的风格,除了粗暴的方式(例如,因为您在不同的命名空间或过程中)。特别是,您无法判断调用是否发生在 if 内部,无论是在表达式部分内部还是在其中一个主体脚本内部。当您需要区分它时,要么通过明确的参数告诉命令,要么将命令分成两个具有不同名称的命令。这样做的一个附带好处是它使您的代码更容易测试。

暂定答案(针对实际问题)

如前所述,无法通过对命令或调用帧进行内省来可靠地检测给定命令 (proc) 的嵌套评估。此外,这需要访问 Tcl 内部(私有 headers 等),并且它仅适用于 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-内部用途无法区分:

if {[myCommand]} { puts "then, here! [myCommand]" } else {puts "notok"}

第二个也不会print-out。只有命令堆栈上的(人工创建的)额外帧可以做到这一点:

if {[myCommand]} { puts "then, here! [apply {{} {myCommand}}]"} else {puts "notok"};

因此,如示例所示,这种方法无法推广。

改进建议

"I shouldn't print command result if it is inside [] brackets etc."

我想 return 接受 Donal 的建议之一,并演示如何通过提供两个单独的命令(一个用于每个上下文,同时保留单个上下文的外观。您可以提供一个计算预期 return 值的主要包装器,并提供一个辅助包装器,其中 "redirects" 将 return 值输出到标准输出(或其他)。

(1) 主要命令:将您现有的 C-implemented 命令变成 打印结果值,但 return 打印结果的命令仅使用 Tcl_SetObjResult。将此命令(或将其别名放入)::tcl::mathfunc::* namespace. Using Critcl,可能如下所示:

critcl::ccommand ::tcl::mathfunc::myCommand {cd interp objc objv} {
    Tcl_Obj* resObj = Tcl_NewIntObj(1);
    Tcl_SetObjResult(interp, resObj);
    return TCL_OK;
}

(2) 在 top-level (::) 或 project-specific 命名空间中创建一个脚本包装器,它具有相同的(非限定的)名称 myCommand:

proc ::myCommand {} {
    puts stdout [uplevel 1 ::tcl::mathfunc::myCommand]
    return
}

包装程序调用主命令,puts 随心所欲地调用结果。 return 将重置解释器的结果。或者,您也可以对其进行 return 编辑,因此包装器在主命令中变为 contract-equivalent。

您现在可以在 [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

所有这些都依赖于 special-purpose 命名空间 ::tcl::mathfunc 以及 [expr] 如何处理其中的命令/过程。对我的好处是:

  • 允许 straight-forward 重构(您的主要命令变得更简单)并且包装器是纯脚本化的。
  • 明确区分使用上下文,甚至在语法上也是如此。
  • 无需对命令的 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 不一定表示 so-named control-flow 结构,以防有人决定搭载此命令名称等)

更好地 re-assess 为什么您首先感觉到这样做的必要性。

The thing is I don't know what to do later cause I can't print command result if it is inside [] brackets etc.

例如,我仍然不清楚是否在 if 上下文中调用命令的客户端对命令的客户端有什么影响。正如 Donal 所写,这应该不会有什么不同。

如果你想在 if 条件下打印出 myCommand 的 return 值,而不改变行为,那么写某事。就像在你的脚本中一样(而不是操纵命令本身):

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