Can TclOO objects be shared/aliases between interpreters? - tcl

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]

Related

Why my variable exists with interp command

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.

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]]
}

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.

How to find a procedure by using the code inside the proc?

Is it possible to find the procedure name by using the content of that procedure?
For example,
proc test {args} {
set varA "exam"
puts "test program"
}
Using the statement set varA, is it possible to find its procedure name test?
Because, I need to find a procedure for which i know the output [it's printing something, i need to find the procedure using that].
I tried many ways like info frame, command. But, nothing helps.
Is it possible to find the procedure name by using the content of that procedure?
Yes. You use info level 0 to get the argument words to the current procedure (or info level -1 to get its caller's argument words). The first word is the command name, as resolved in the caller's context. That might be enough, but if not, you can use namespace which inside an uplevel 1 to get the fully-qualified name.
proc foo {args} {
set name [lindex [info level 0] 0]
set FQname [uplevel 1 [list namespace which $name]]
# ...
}
Note that this does not give you the main name in all circumstances. If you're using aliases or imported commands, the name you'll get will vary. Mostly that doesn't matter too much.
With info proc, we can get the content of a procedure which may helps you in what you expect.
The following procedure will search for the given word in all the namespaces. You can change it to search in particular namespace as well. Also, the search word can also be case insensitive if altered in terms of regexp with -nocase. It will return the list of procedure names which contains the search word.
proc getProcNameByContent {searchWord} {
set resultProcList {}
set nslist [namespace children ::]; # Getting all Namespaces list
lappend nslist ::; # Adding 'global scope namespace as well
foreach ns $nslist {
if {$ns eq "::"} {
set currentScopeProcs [info proc $ns*]
} else {
set currentScopeProcs [info proc ${ns}::*]
}
foreach myProc $currentScopeProcs {
if {[regexp $searchWord [info body $myProc]]} {
puts "found in $myProc"
lappend resultProcList $myProc
}
}
}
return $resultProcList
}
Example
% proc x {} {
puts hai
}
% proc y {} {
puts hello
}
% proc z {} {
puts world
}
% namespace eval dinesh {
proc test {} {
puts "world is amazing"
}
}
%
% getProcNameByContent world
found in ::dinesh::test
found in ::z
::dinesh::test ::z
%