Why my variable exists with interp command - tcl

I think I’ve read all the topics on this command through stackoverflow, wiki and others... English is not my native language, so I must have misunderstood.
I want independent execution context. But I don't understand when I execute this script
proc make {w} {
global global_var
if {[info exists global_var]} {
puts "yes but I don't understand..."
}
set global_var $w
}
set foo [interp create -safe]
interp alias $foo hello {} make
interp eval $foo {
hello .f1
}
set foo1 [interp create -safe]
interp alias $foo1 hello1 {} make
interp eval $foo1 {
hello1 .f2
}
my var global_var exists, since I create 2 different contexts foo & foo1. My variable should never have existed.

An alias between a source and target interpreter creates a command-level link, but the linked command (make) will always be executed and evaluated in the target interpreter (your main interp), and not the calling or source interpreter.
Therefore, the hello call in child interp $foo will execute make in the main interp, and so does the second call hello1 in another child interp. Hence, the variable exists.
Besides: Your debugging puts would eventually fail if executed in a safe interp, because there would not be a stdout channel available.
You have to provide for common procedure definition of make in any child interp, and then execute those per-interp procs. Watch:
set initScript {
proc make {w} {
global global_var
if {[info exists global_var]} {
# yes but I don't understand...
}
set global_var $w
}
}
set foo [interp create -safe]
interp eval $foo $initScript
interp alias $foo hello $foo make
interp eval $foo {
hello .f1
}
set foo1 [interp create -safe]
interp eval $foo1 $initScript
interp alias $foo1 hello1 $foo1 make
interp eval $foo1 {
hello1 .f2
}
Now executing, for a second time:
interp eval $foo1 {
hello1 .f2
}
... will now enter your if-conditional script within make, as the variable was found existing in the second child interp.
Additional remarks:
You may still use aliases within the child interpreters.
You may better organise complex definition scripts as a Tcl package or Tcl module to be sourced from each child interpreter.

Related

How do you access a procedure's local variables in a namespace eval {} inside that same procedure?

I'm sure I'm just being stupid but would you please tell me how to get access to $name inside the namespace in order to set variable n to $name? I can only find how to do this when the procedure is in the namespace but not the other way 'round. No matter what I try, this errors stating no such variable name. Thank you.
proc getNS {name} {
namespace eval ns::$name {
variable n $name
}
}
This works but isn't really an answer unless the answer is simply that it cannot be done directly. Got it from this SO question. Bryan Oakley gave the answer but used [list set...] instead of [list variable...] and that will fail if there is a global variable of the same name. (It will modify the global rather than creating a new variable in the namespace.) It may have been different, of course, in 2009 when that answer was provided.
proc getNS {name} {
namespace eval ns::$name [list variable n $name]
namespace eval ns::$name {
variable a abc
}
}
set n xyz
getNS WEBS
chan puts stdout "ns n: $ns::WEBS::n; a $ns::WEBS::a, global n: $n"
# => ns n: WEBS; a: abc; global n: xyz
You can just use set with a fully qualified variable name that uses the desired namespace:
proc getNS {name} {
namespace eval ns::$name {} ;# Create namespace if it doesn't already exist
set ns::${name}::n $name
}
getNS foo
puts $ns::foo::n ;# foo
Another way is to use uplevel to refer to the scope of the proc that calls namespace eval:
proc getNS {name} {
namespace eval ns::$name {
set n [uplevel 1 {set name}]
}
}

Tcl: How to get namespace and procedures of calling namespace?

I have some generic procedure. I would like this procedure to be able to get the name of the namespace and names of the procedures within the namespace where this procedure is called.
I have tried following code:
proc register {} {
puts [info procs]
puts [namespace current]
}
namespace eval Foo {
proc bar {} {
puts bar
}
proc _baz {} {
puts baz
}
register
}
However, this prints results for the namespace where register is defined, not for the namespace where it is executed. It looks like there are no dedicated commands for these tasks or at least these are not info or namespace commands.
To get information about the calling context, use uplevel:
proc register {} {
puts [uplevel 1 [list info procs]]
puts [uplevel 1 [list namespace current]]
}

Can TclOO objects be shared/aliases between interpreters?

I'm implementing DSL processing. I'm using a safe interpreter to source the input file.
As part of the processing, I'm building an object.
something like:
set interp [interp create -safe]
interp expose $interp source
interp eval $interp {
oo::class create Graph { # ...
}
# add domain-specific commands here
proc graph {definition} {
global graph
$graph add $definition
}
# and so on
set graph [Graph new]
}
interp eval $interp source $graphFile
Is there a mechanism to alias the $graph object into the main interpreter?
Aliases are commands, not objects. However, for calling (as opposed to modifying definitions or making subclasses, etc.) you can set an alias that points to the object in the other interpreter:
oo::class create Example {
variable count
method bar {} { return "this is [self] in the method bar, call [incr count]" }
}
Example create foo
interp create xyz
interp alias xyz foo {} foo
xyz eval {
puts [foo bar]
}
# this is ::foo in the method bar, call 1
Individual methods can also go across interpreters (with some limitations) if you forward the method call to an alias that crosses the interpreter boundary. This allows for all sorts of shenanigans.
oo::class create Example {
variable count
method bar {caller} { return "this is $caller in the method bar, call [incr count]" }
}
Example create foo
interp create xyz
interp alias xyz _magic_cmd_ {} foo bar
interp eval xyz {
oo::class create Example {
forward bar _magic_cmd_ pqr
}
Example create grill
grill bar
}
# this is pqr in the method bar, call 1
What I ended up with:
oo::class create Graph { # ...
}
set graph [Graph new]
set interp [interp create -safe]
interp expose $interp source
interp alias $interp graphObj {} $graph
interp eval $interp {
# add domain-specific commands here
proc graph {definition} {
graphObj add $definition
}
# and so on
}
interp eval $interp source $graphFile
puts [$graph someMethod]

How to effectively override a procedure-local variable in TCL

So I have the following situation:
$ ls -l
-r--r----- 1.tcl
-rw-rw---- 2.tcl
$ cat 1.tcl
proc foo {args} {
puts "$bar"
}
and I need to make 1.tcl print something other than "can't read \"bar\"". In a good programming language, the obvious solution would be
$ cat > 2.tcl
set -global bar "hello, world"
foo
What would be a reasonable workaround in TCL? Unfortunately the real foo is a long function that I can't really make a copy of or sed to a temporary file at runtime.
You can do this for your specific example
$ cat 2.tcl
source 1.tcl
set bar "Hello, bar!"
# add a "global bar" command to the foo procedure
proc foo [info args foo] "global bar; [info body foo]"
foo
$ tclsh 2.tcl
Hello, bar!
Clearly this doesn't scale very well.
If the variable is simply undefined, the easiest way would be to patch the procedure with a definition:
proc foo [info args foo] "set bar \"hello, world\" ; [info body foo]"
You can also accomplish this using a read trace and a helper command. This removes the problem I mentioned above, where local assignments destroy the value you wanted to inject.
The original procedure, with an added command that sets the local variable to a value which is later printed.
proc foo args {
set bar foobar
puts "$bar"
}
% foo
foobar
Create a global variable (it doesn't matter if the name is the same or not).
set bar "hello, world"
Create a helper command that gets the name of the local variable, links to it, and assigns the value of the global variable to it. Since we already know the name we could hardcode it in the procedure, but this is more flexible.
proc readbar {name args} {
upvar 1 $name var
global bar
set var $bar
}
Add the trace to the body of the foo procedure. The trace will fire whenever the local variable bar is read, i.e. something attempts to retrieve its value. When the trace fires, the command readbar is called: it overwrites the current value of the variable with the globally set value.
proc foo [info args foo] "trace add variable bar read readbar; [info body foo]"
% foo
hello, world
If one doesn't want to pollute the namespace with the helper command, one can use an anonymous function instead:
proc foo [info args foo] [format {trace add variable bar read {apply {{name args} {
upvar 1 $name var
global bar
set var $bar
}}} ; %s} [info body foo]]
Documentation:
apply,
format,
global,
info,
proc,
puts,
set,
trace,
upvar,
Syntax of Tcl regular expressions
source 1.tcl
try {
foo
} on error {err res} {
set einfo [dict get $res -errorinfo]
if { [regexp {no such variable} $einfo] } {
puts "hello, world"
return -code 0
} else {
puts $einfo
return -code [dict get $res -code]
}
}
Tcl's procedures do not resolve variables to anything other than local variables by default. You have to explicitly ask for them to refer to something else (e.g., with global, variable or upvar). This means that it's always possible to see at a glance whether non-local things are happening, and that the script won't work.
It's possible to override this behaviour with a variable resolver, but Tcl doesn't really expose that API in its script interface. Some extensions do more. For example, it might work to use [incr Tcl] (i.e., itcl) as that does that sort of thing for variables in its objects. I can't remember if Expect also does this, or if that uses special-cased code for handling its variables.
Of course, you could get really sneaky and override the behaviour of proc.
rename proc real_proc
real_proc proc {name arguments body} {
uplevel 1 [list real_proc $name $arguments "global bar;$body"]
}
That's rather nasty though.

tcl changing a global variable while in a child interpreter (interp)

I am trying to use a TCL program to read TCL modulefiles and translate them into another language. This has worked quite well until now. For reasons too complicated explain I have to treat "puts stderr" differently in different parts of the modulefile. I am asking for help in trying to figure out a way to do this.
Below is an extremely abbreviated modulefile called "modfile". This "modfile" is translated or "sourced" by a second tcl program.
proc ModulesHelp { } {
 puts stderr "(1) This is a help message"
}
puts stderr "(2) Here in modfile"
The puts statement inside ModulesHelp has to be treated differently from the second puts statement. Note that any solution CAN NOT CHANGE "modfile". That file is not under my control.
Here is my attempt at a solution:
#!/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
To run this I do: $ ./try.tcl modfile where obviously the above script is "try.tcl" and the modulefile is "modfile". I am running this on a linux system with tcl 8.4.
What I would like to have happen is that at the line labelled "HERE 2" I like to somehow change the global variable of "putMode" from "normal" to "InHelp" so that I can change the behavior at the line labelled "HERE 1". No matter what I have tried to do I can not change the value of putMode at "HERE 1" by doing something at "HERE 2". The puts statement at "HERE1 always says "normal".
Using a global variable seems like the easiest solution but if someone could show me how to use namespaces or some other technique, I'll be happy with that as well.
Thanks for any insight.
I greatly appreciate the time that others have looked at my question. I am trying to use the proposed solution and I'm not quite seeing it. Here is my new attempt at a solution (This doesn't work at all). Can someone suggest how I modify this code to change "putMode" to inHelp where "HERE 2" is? Also is there something special that needs to go where "HERE 1" is?
#!/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
The problem is that the slave and the master are different interpreters. This means that every interpreter has it's own
commands
variables
namespaces
channels
You can't simply change a variable in the master from the slave, so the easiest solution would be:
interp alias $slave InHelp {} set ::putMode InHelp
and calling this alias instead.
Some other notes:
An other option would be to change the puts alias when InHelp is called. Example
proc InHelp {slave} {
interp alias $slave puts {} HelpPuts
}
and using it with interp alias $slave {} InHelp $slave
You don't have to assign a name for the slave. Just do
set slave [interp create]
Single words don't have to be quoted, so
list "a" "b" "c"
is equal to
list a b c
If you need argument expansion (and use at least Tcl 8.5) use {*}$argv instead eval.
But because execute-modfile only accept one argument, execute-modfile [lindex $argv 0] should do the job.
As Johannes writes, variables are entirely separate in different interpreters; they're not shared at all.
However, you can use trace and some aliases to couple things together. I'll show how to do it for a simple scalar variable (with the parent having an array of them, presumably one for each child interpreter), under the assumption that you never want to have the setting of the variable in the master interpreter trigger a trace in the child.
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}]
}
}
This makes it so that whenever the child interpreter reads or writes the putMode variable, the read/write gets reflected into the master.
It's easier to map a command (via an alias) though, and if you were using Tcl 8.6 I'd suggest stacking and unstacking custom transformations on stderr instead. (But that's a massively more sophisticated technique.)
Thanks for all the help. It has taken me a while to understand what was being proposed. Here is the code that does what I want:
#!/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
Thanks again.