【问题标题】:Embedded Tcl: How to detect the evaluation of expression?Embedded Tcl:如何检测表达式的评估?
【发布时间】:2018-11-05 11:21:02
【问题描述】:

我们有一个内置我们的 C/C++ 应用程序的 Tcl,在我们的代码中有一个地方我们必须检查命令是否 嵌套,这意味着命令结果稍后设置为:“::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 或有关当前执行命令的某种附加信息。难道我做错了什么?问题是我不知道以后要做什么,因为如果命令结果在 [] 括号内等,我“不应该”打印它。

【问题讨论】:

  • 恐怕我不清楚你想要达到什么目的。你能请。提供一个 C 程序(一个 C 实现的 Tcl 命令?)的最小工作示例,它以描述的方式练习 Tcl_SetResult 和 Tcl_GetCommandInfo?根据“命令”是否作为脚本 if 的一部分执行,预期的结果是什么?
  • 更新问题。

标签: embedded tcl expression evaluation


【解决方案1】:

暂定答案(关于实际问题)

如前所述,无法通过自省命令或调用帧来可靠地检测给定命令 (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

【讨论】:

  • 更多背景知识:您获得相同的级别(在info level 的意义上),因为if-scripts 和周围的脚本在相同的调用堆栈级别执行(例如,对于相同的变量范围)。注意callstack levels and callstack frames之间的区别
  • 其实这不是客户抱怨的bug,而是工作同事给我这个任务看,而且是看样子,所以是:“我不应该打印命令如果它在 [] 括号内等,则结果。”不管怎样,谢谢你的回答,我明天会努力做到的。
  • 我不确定我应该在我的代码中检查什么样的数据,我再次更新了问题,你能看一下吗?
  • @A.Michałek 我试图提出一些具体的建议,但一般建议是重新考虑问题并从不同的角度考虑(请参阅我的改进建议。)跨度>
  • 我不知道如何感谢您的帮助。这对我帮助很大。现在我明白了。我只是认为也许还有另一种如何检测嵌套评估的解决方案,但现在正如你再次所说,我不可能做其他事情。另外,我会尝试你的改进建议。谢谢大佬。
【解决方案2】:

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

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


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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-20
    • 1970-01-01
    • 1970-01-01
    • 2021-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多