1

我正在尝试使用 TCL 程序来读取 TCL 模块文件并将它们翻译成另一种语言。到目前为止,这一直运作良好。由于解释过于复杂的原因,我必须在模块文件的不同部分以不同的方式处理“放置标准错误”。我正在寻求帮助,试图找出一种方法来做到这一点。

下面是一个极其缩写的模块文件,称为“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”,模块文件是“modfile”。我正在使用 tcl 8.4 的 linux 系统上运行它。

我想要发生的是,在标记为“HERE 2”的行中,我想以某种方式将“putMode”的全局变量从“正常”更改为“InHelp”,以便我可以更改标记为“HERE”的行的行为1"。无论我尝试做什么,我都无法通过在“HERE 2”处执行某些操作来更改“HERE 1”处的 putMode 值。“HERE1”处的 puts 语句总是说“正常”。

使用全局变量似乎是最简单的解决方案,但如果有人可以向我展示如何使用命名空间或其他一些技术,我也会对此感到满意。

感谢您的任何见解。


我非常感谢其他人看到我的问题的时间。我正在尝试使用建议的解决方案,但我不太明白。这是我对解决方案的新尝试(这根本不起作用)。有人可以建议我如何修改此代码以将“putMode”更改为“HERE 2”所在的 inHelp 吗?还有什么特别的东西需要去“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
4

3 回答 3

1

问题是slave和master是不同的解释器。这意味着每个口译员都有自己的

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

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

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),请使用{*}$argveval。
    但是因为 execute-modfile 只接受一个参数,execute-modfile [lindex $argv 0]所以应该可以完成这项工作。

于 2013-05-22T20:08:52.333 回答
1

正如Johannes 所写,变量在不同的解释器中是完全独立的。它们根本不共享。

但是,您可以使用trace和一些别名将事物耦合在一起。我将展示如何为一个简单的标量变量执行此操作(父母有一个数组,大概每个子解释器一个),假设您永远不想在主解释器触发器中设置变量在孩子身上留下一丝痕迹。

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(但这是一种更为复杂的技术。)

于 2013-05-23T09:11:06.093 回答
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

再次感谢。

于 2013-05-23T21:22:51.107 回答