tcl在子解释器(interp)中更改全局变量

时间:2013-05-22 19:32:01

标签: tcl sandbox

我正在尝试使用TCL程序来读取TCL模块文件并将它们翻译成另一种语言。到目前为止,这种方法运作良好。由于太复杂的原因,我不得不在模块文件的不同部分对待“put stderr”。我正在寻求帮助,试图找到一种方法来做到这一点。

下面是一个名为“modfile”的极简缩模块文件。这个“modfile”由第二个tcl程序翻译或“获取”。

    proc ModulesHelp { } {
      puts stderr "(1) This is a help message"
    }
    puts stderr "(2) Here in modfile"

ModulesHelp中的puts语句必须与第二个puts语句区别对待。请注意,任何解决方案都不能改变“modfile”。那个档案不在我的掌控之中。

以下是我尝试解决方案:

    #!/usr/bin/env tclsh
    proc myPuts { stream msg } {
        global putMode
        puts stdout "putMode: $putMode"    # <====== HERE 1
        puts stdout $msg
    }

    proc report { message } {
        puts stderr "$message"
    }

    proc execute-modulefile { m } {
        global MODFILE putMode

        set putMode "normal"
        set slave   "__mod"

        interp create $slave
        interp alias  $slave puts {} myPuts
        interp alias  $slave report {} report
        interp eval   $slave {global putMode }
        interp eval   $slave [list "set" "putMode" $putMode]
        interp eval   $slave [list "set" "m"       $m]

        set errorVal [interp eval $slave {
        set sourceFailed [catch {source $m } errorMsg]
            if {[info procs "ModulesHelp"] == "ModulesHelp" } {
                set putMode "InHelp"     # <======= HERE 2
                ModulesHelp
            }
            if {$sourceFailed} {
                report $errorMsg
                return 1
            }
        }]
        interp delete $slave
        return $errorVal
    }

    eval execute-modulefile $argv

要运行这个,我执行:$ ./try.tcl modfile,显然上面的脚本是“try.tcl”,而modulefile是“modfile”。我在tcl 8.4的linux系统上运行它。

我希望发生的是,在标有“HERE 2”的行中,我喜欢以某种方式将“putMode”的全局变量从“normal”更改为“InHelp”,以便我可以更改行中的行为标有“HERE 1”。无论我试图做什么,我都不能通过在“HERE 2”做一些事来改变putMode在“HERE 1”的值。置于“HERE1”的声明总是说“正常”。

使用全局变量似乎是最简单的解决方案,但如果有人可以告诉我如何使用命名空间或其他技术,我也会对此感到高兴。

感谢您的任何见解。


我非常感谢其他人查看我的问题的时间。我正在尝试使用建议的解决方案而我并没有看到它。这是我对解决方案的新尝试(这根本不起作用)。有人可以建议我如何修改此代码以将“putMode”更改为inHelp,其中“HERE 2”是?还有一些特殊的东西需要去“HERE 1”吗?

    #!/usr/bin/env tclsh
    proc myPuts { stream msg } {
        global putMode
        puts stdout "putMode: $putMode"  # <=== HERE 1
        puts stdout $msg
    }

    proc report { message } {
        puts stderr "$message"
    }


    proc PutModeTrace {childInterp operation realPutMode} {
        # Alias the main array element for the purposes of this procedure
        upvar \#0 PutMode($childInterp) realPutMode
        if {$operation eq "read"} {
            interp eval $childInterp [list set putMode $realPutMode]
        } elseif {$operation eq "write"} {
            set realPutMode [interp eval $childInterp {set putMode}]
        }
    }
    proc execute-modulefile { m } {
        global MODFILE putMode

        set putMode "normal"
        set slave   [interp create]

        interp alias  $slave puts {} myPuts
        interp alias  $slave report {} report
        interp eval   $slave {global putMode }
        interp eval   $slave [list "set" "putMode" $putMode]
        interp eval   $slave [list "set" "m"       $m]
        interp eval   $slave [list "set" "slave"   $slave]
        interp eval   $slave {trace add variable putMode {read write} PutModeTrace}
        interp alias  $slave PutModeTrace {} PutModeTrace $slave
        set errorVal [interp eval $slave {
        set sourceFailed [catch {source $m } errorMsg]
            if {[info procs "ModulesHelp"] == "ModulesHelp" } {
                set start "help(\[\["
                set end   "\]\])"
                PutModeTrace $slave "write" "inHelp"  # <=== HERE 2
                puts stdout $start
                ModulesHelp
                puts stdout $end
            }
            if {$sourceFailed} {
                report $errorMsg
                return 1
            }
        }]
        interp delete $slave
        return $errorVal
    }

    eval execute-modulefile $argv

3 个答案:

答案 0 :(得分:1)

问题是奴隶和主人是不同的口译员。这意味着每个翻译都有自己的

  • 命令
  • 变量
  • 命名空间
  • 信道

您不能简单地从主服务器更改主服务器中的变量,因此最简单的解决方案是:

interp alias $slave InHelp {} set ::putMode InHelp

并改为调用此别名。

其他一些说明:

  • 另一种选择是在调用puts时更改InHelp别名。实施例

    proc InHelp {slave} {
         interp alias $slave puts {} HelpPuts
    }
    

    并将其与interp alias $slave {} InHelp $slave

  • 一起使用
  • 您不必为从站指定名称。只是做

    set slave [interp create]
    
  • 不必引用单个单词,所以

    list "a" "b" "c"
    

    等于

    list a b c
    
  • 如果您需要参数扩展(并且至少使用Tcl 8.5),请使用{*}$argv代替eval。
    但是因为execute-modfile只接受一个参数,execute-modfile [lindex $argv 0]应该完成这项工作。

答案 1 :(得分:1)

作为Johannes writes,变量在不同的解释器中是完全独立的;它们根本没有共享。

但是,您可以使用trace和一些aliases将事物连接在一起。我将展示如何为一个简单的标量变量(父类具有它们的数组,可能每个子解释器一个),假设您永远不想在主解释器触发器中设置变量在孩子身上留下痕迹。

interp eval $child {trace add variable putMode {read write} PutModeTrace}
interp alias $child PutModeTrace {} PutModeTrace $child
proc PutModeTrace {childInterp varName elementName operation} {
    # Ignore the elementName argument

    # Alias the main array element for the purposes of this procedure
    upvar \#0 PutMode($childInterp) realPutMode
    if {$operation eq "read"} {
        interp eval $childInterp [list set putMode $realPutMode]
    } elseif {$operation eq "write"} {
        set realPutMode [interp eval $childInterp {set putMode}]
    }
}

这使得无论何时子解释器读取或写入putMode变量,读/写都会反映到主变量中。


虽然映射命令(通过别名)更容易,但如果您使用的是Tcl 8.6,我建议在stderr上堆叠和取消堆栈custom transformations。 (但这是一种更为复杂的技术。)

答案 2 :(得分:0)

感谢所有帮助。我花了一段时间来了解提出的建议。这是执行我想要的代码:

     #!/usr/bin/env tclsh
    proc myPuts { stream msg } {
        global putMode
        if {$putMode != "inHelp"} {
            puts stderr $msg
        } else {
            puts stdout $msg
        }
    }

    proc report { message } {
        puts stderr "$message"
    }


    proc setPutMode { value } {
        global putMode
        set putMode $value
    }


    proc execute-modulefile { m } {
        global MODFILE putMode

        set putMode "normal"
        set slave   [interp create]

        interp alias  $slave puts {} myPuts
        interp alias  $slave setPutMode {} setPutMode
        interp alias  $slave report {} report
        interp eval   $slave {global putMode }
        interp eval   $slave [list "set" "putMode" $putMode]
        interp eval   $slave [list "set" "m"       $m]
        interp eval   $slave [list "set" "slave"   $slave]
        interp eval   $slave {trace add variable putMode {read write} PutModeTrace}
        interp alias  $slave PutModeTrace {} PutModeTrace $slave
        set errorVal [interp eval $slave {
        set sourceFailed [catch {source $m } errorMsg]
            if {[info procs "ModulesHelp"] == "ModulesHelp" } {
                set start "help(\[\["
                set end   "\]\])"
                setPutMode "inHelp"
                puts stdout $start
                ModulesHelp
                puts stdout $end
                setPutMode "normal"
            }
            if {$sourceFailed} {
                report $errorMsg
                return 1
            }
        }]
        interp delete $slave
        return $errorVal
    }

    eval execute-modulefile $argv

再次感谢。