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

时间:2018-11-05 11:21:03

标签: embedded tcl expression evaluation

我们在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; # -> nLevel == 1
if {[our_command] eq "sth"} {do_sth}; # -> also nLevel == 1

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

2 个答案:

答案 0 :(得分:0)

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

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


根据命令的调用上下文是什么,改变命令的行为被认为是 极其糟糕的样式 ,例如,总的来说(例如,因为您处于不同的名称空间或过程中)。特别是,您无法确定在if内是在表达式部分内还是在主体脚本之一内发生了调用。当需要区分时,可以通过显式参数告诉命令,也可以将命令分为两个名称。这样做的附带好处是它使您的代码更容易测试。

答案 1 :(得分:0)

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

如前所述,无法通过内省命令或调用帧来可靠地检测给定命令(proc)的嵌套评估。另外,这需要访问Tcl内部(专用标头等),并且仅适用于Tcl 8.6+(如果这对您很重要)。至少可以通过以下操作检测到if版和非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"}

第二个也不打印。在命令堆栈上只有一个(人工创建的)额外框架会做到这一点:

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

因此,如示例所示,这种方法并不通用。

改进建议

  

“如果命令结果在[]方括号内,则不应该打印命令结果。”

我想回到Donal的建议之一,并演示如何通过提供两个单独的命令(对于每个上下文一个),同时保留命令的使用性,轻松地解开命令(myCommand)的不同用法上下文。看起来没有一个。您可以提供一个主要的工具来计算预期的返回值,并提供一个辅助包装器,该包装器将返回值“重定向”到stdout(或其他)。

(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将重置口译员的结果。另外,您可能还返回了它,因此包装器与主命令成为合同等效的。

您现在可以在myCommand条件和非[expr]条件这样的[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)明确指出的那样,它具有强烈的气味,并可能引起许多误报(例如,[myCommand]结构中的任何地方的ifif不一定表示所谓的控制权-流结构,以防万一有人决定使用此命令名称等。

首先,最好重新评估一下为什么需要这样做。

  

问题是我不知道以后要做什么,因为我无法打印命令   如果在[]方括号等内,则返回结果。

例如,对于我来说,尚不清楚是否在if上下文中调用命令,这对您的命令客户端有什么不同。正如Donal所写,这应该不会有所作为。

如果要在myCommand条件下打印出if的返回值,而又不改变其行为,请编写sth。就像在脚本中一样(而不是操纵命令本身):

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