How do I use the eval statement in tcl - tcl

So basically in my tcl script I generate a line of tcl code that I have to execute( ie I come up with this instruction during runtime). Once I have the tcl instruction say for example puts "hello world $test_var". How do I execute this using tcl?
Do I use the eval command or what?

The eval command is a reasonable way of doing this, but you might want to consider using catch instead, since that will trap any problems found during the evaluation of the generated code, for example like this:
# Generate the code somehow
set variable {puts "hello word $test_var"}
# Execute and trap problems
if {[catch $variable resultMsg]} {
# An error (or other exception) happened
puts stderr "BOOM, Error! $resultMsg"
}

Instead of using [eval] which works perfectly well, in newer versions of Tcl you can use the {*} syntax.
For example:
set cmd "puts"
lappend cmd "abcd ef"
{*}$cmd
Note that it's a good habit to use list commands to build up your command so you wont run into quoting issues.

I'm afraid you don't have another reasonable choice than to use the eval command, e.g.:
set variable {puts "hello world $test_var"}
eval $variable

Related

Reading cmd arguments in TCL file

I am trying to run a tcl script through .bat file. I want to read some cmd arguments in the tcl script. Below is my code:
Command to run:
D:\Cadence\Sigrity2021.1\tools\bin\PowerSI.exe -tcl abcd.tcl %new_var%.spd %new_file_name%
Below is how I am trying to read the variable in the tcl file:
sigrity::open document [lindex $argv 0] {!}
It open up the Cadence Sigrity, but I see the below error:
How do I read cmd argument in tcl?
If you have no other way to do it that you can find (and it sounds like that might be the case) then you can fake it by writing a helper file with content like this, filling in the real arguments in the appropriate places:
# Name of script to call
set ::argv0 "abcd.tcl"
# Arguments to pass
set ::argv {}
lappend ::argv "%new_var%.spd"
lappend ::argv "%new_file_name%"
# Number of arguments (rarely used)
set ::argc [llength $::argv]
# Do the call
source $::argv0
Then you can pass that file to PowerSI and it will set things up and chain to the real file. It's messy, but practical.
If you're writing this from Tcl, use the list command to do the quoting of the strings (instead of putting them in double quotes) as it will do exactly the right thing for you. If you're writing the file from another language, you'll want to make sure you put backslashes in before \, ", $ and [ characters. The fiddlyness of doing that depends on your language.

invalid command name "2001:172:16:21::36"

I need to assign my final variable with the following string UDP6:[2001:172:16:21::36]
set ipAddr1 "UDP6,2001:172:16:21::36"
set ipAddrArr [split $ipAddr1 ","]
set ipAddrArr11 [lindex $ipAddrArr 0]
set ipAddrArr12 [lindex $ipAddrArr 1]
set tmp ":\["
set ipAddr1Part [join "$ipAddrArr11 $ipAddrArr12" $tmp]
set tmp1 "]"
set ipAddrFinal [join "$ipAddr1Part$tmp1"]
When I run the tcl script, it gives invalid command name as 2001:172:16:21::36.
I have printed ipAddrFinal value , it gives the expected one UDP6:[2001:172:16:21::36]
pls help me out? what am I missing
The script as you have written it works fine; it assigns the string UDP6:[2001:172:16:21::36] to the variable ipAddrFinal. However, since it contains characters that are Tcl metacharacters in some contexts, I suspect that you are then using the string in an unsafe way, most likely with eval or possibly with subst or uplevel. If you look at the stack trace of the error (in the errorInfo global variable by default) you should be told pretty exactly where the offending code is; it might give a few places you need to look, but it usually isn't too hard to hunt down where the problem originates from.
If your problem comes from uplevel, you are probably going to need to use list to construct a command; 99.99% of all problems with uplevel are handled that way. If your problems come from eval, the chance's good that you need to switch to using expansion syntax. If your problems come from subst or are otherwise still deeply confusing, check back with us (with your stack trace if you are not sure where the problem is coming from).
Example of a fix for eval:
Change:
set action "puts \"IP\\ address\\ is\\ $ipAddrFinal\""
eval $action
to:
set action [list puts "IP address is $ipAddrFinal"]
{*}$action
NB: The error from doing the eval is a reasonable example too:
invalid command name "2001:172:16:21::36"
while executing
"2001:172:16:21::36"
("eval" body line 1)
invoked from within
"eval $action"
Note that it says that it's in an eval, and that points squarely to unsafe script construction. The list command does safe script construction as one of its bonus superpowers.

give arguments to a script with eval

I don't really understand how to use the eval arguments.
If the script I want to eval is :
set myscript { puts $::argv }
Then I want to call my script like this :
eval $myscript anArgument
And I expect the output to be "anArgument", but instead I have :
can not find channel named ""
while evaluating {eval $script vvv}
When you invoke eval, the command concatenates all its arguments and attempts to evaluate the resulting list (or string if you will, it's the same thing here). So, first the arguments { puts $::argv } and anArgument are concatenated into the list {puts $::argv anArgument}, and then the interpreter tries to evaluate that. If the value of the global variable argv is an empty list, the actual command invoked will be equivalent to puts {} anArgument. puts will try to use {} as a channel identifier to output to, fail and leave an error message.
Now, if what you wanted to do was to pass anArgument to myscript and then eval it as puts anArgument, you should instead write
set myscript {puts $myarg}
set myarg anArgument
eval $myscript
In the first line, the evaluation of $myarg is postponed because of the quoting braces which turn $ into a regular text character. The variable myarg is then set to a value (this can happen anywhere in the code as long as it comes before the eval). In the third line, the script is evaluated, and at that point the argument $myarg is replaced by the value anArgument, which is then printed.
The sort of invocation you attempted is possible, but then you need to use apply instead of eval, and a closure (anonymous function) instead of a script.
set myfunc {myarg {puts $myarg}}
apply $myfunc anArgument
The global variable argv does not pass arguments to a script which is passed to eval: when tclsh or wish is started by the operating system, any command line arguments given are placed in argv, and the value is never changed during execution unless you change it yourself (don't do that, it's just confusing).
Documentation: apply, eval, puts, set
argv is documented here.

Command to return library (not work) name of a path in modelsim

I want to find a way to return the name of a library of a certain path in a VHDL Design in Modelsim.
Given a VHDL Design with a path like "/mega_tb/D0". This is compiled in a library that is NOT 'work', say "libnwork".
I can of course take a look in my 'do' file to get the correct lib name. Or I can search in ModelSim's Library tab. But I want to have or create a modelsim command which I can later use in a Tcl script, to get the correct library name.
One of the easiest ways to find something in a Tcl script file – which is all a Modelsim “do” file is — is to evaluate it. Tcl's very good at that. Of course, you don't want to have the commands do all the conventional things. Instead, we'll evaluate in a context where we can make everything do nothing except for the command that produces the information we want:
# Set up our evaluation context, 'worker'
interp create worker -safe
interp eval worker {proc unknown args {}}; # Our do-nothing handler
interp alias worker theInterestingCommand {} ourHandler
proc ourHandler args {
puts "We were called with: $args"
}
# Parse the file!
set f [open /the/file.tcl]
interp eval worker [read $f]
# Clean up
close $f
interp delete worker
Now you just have to make theInterestingCommand have the right name and extract the interesting information from the arguments. Which should be relatively easy…
Te only way I've found is to use the command
write report -tcl
This prints a long list where I have search for the lib names with regexps.
Something like
set data [ write report -tcl]
foreach_regexp { _ type lib entity} $data{
if {$type == "Entity" && $entity == [entity_of_path /mega_tb/D0] } {
....
}
}
Where I of course had to define my "foreach_regexp" procedure and my "entity_of_path" procedure. I then can use something like regsub to extract the library name.
I am still looking for a better and easier way.

how to use Tcl's (interp) bgerror

I'm trying to run tclhttpd in a slave interpreter but slightly modified so as to run within a tclkit. The code below "runs" (I can hit http://localhost:8015) but never reaches the puts line at the bottom because "the server does not return, it enters [vwait forever]". But when I try "the after 0 trick", e.g. prepending "after 0 " to the line "$httpd eval $cmd", the server does not run at all, so I presume "errors have to be handled by bgerror"
However I cannot find good examples of how to use bgerror, plus my research shows that now the convention is to use "interp bgerror". Please see the first couple of examples returned by http://www2.tcl.tk/_/gsearch?S=bgerror; the first link contains the verbiage "fill in useful tricks and examples for using bgerror" but then there are no samples I can discern how to apply, and the second link concludes "I am interested in examples how this is supposed to be used."
package require starkit
starkit::startup
set httpd_args [list]
set httpd [interp create]
$httpd eval "set argc [llength $httpd_args]"
set cmdargv "set argv [list $httpd_args ]"
$httpd eval "set topdir $starkit::topdir"
$httpd eval $cmdargv
set cmd [list source [file join $starkit::topdir bin/httpd.tcl]]
$httpd eval $cmd
puts "if seeing this controlled has returned"
Completely edited based on the OP's comments...
The after 0 trick is the following line:
after 0 $httpd eval $cmd
What this does is tell the interp to add the command in question ($http eval $cmd) to the event queue, which means it will run once the event loop is started (or returned to if it's already started). You can see the reliance on the event loop in the following comment from that page (by Jacob Levy):
I should note that this depends on the event loop being active.
My guess is that you're running a plain Tclsh, which means you never enter the event loop (the Wish shell enters the event loop at the end of the script, the Tcl shell does not). The standard way to enter the event loop is to run the following command once you get to the end of your Tcl code:
# Enter the event loop and stay in it until someone
# sets the "forever" variable to something
vwait forever
That being said, anything you have after the vwait will not run until after the event loop is exited. If you want the httpd to run in parallel to your code, you need to either:
Use multiple threads, or write your ... which really isn't that hard
code to be event based ... which requires you understand even based programming well enough to prevent pieces of code from being starved of execution time.
Hope that helps.
I don't quite understand the question you are asking. It sounds like your goal is to start up an http server in one interpreter but somehow interact with the main interpreter. Is that right? If so, what does that have to do with bgerror?
Are you aware that even though you are running the server in a separate interpreter, it is not running in a separate thread? That is, you can't (*) interact with the main interpreter while either interpreter is blocked by a vwait.
(*) you can, if your interaction takes the form of Tk widgets that also take advantage of the event loop
As for how to use bgerror, There are a couple of ways that it works. The default mechanism calls the function 'bgerror" which you may define to do whatever you want. It takes a single string (the text of an error message) and does something with it. That something could be to print the error to stdout, show it in a dialog, write it to a file, etc.
As an example, consider this interactive session:
% proc bgerror {s} {puts "hey! I caught an error: $s"}
% # after 30 seconds, throw an error
% after 30000 {error "this is an error"}
after#0
% # after 40 seconds, terminate the event loop
% after 40000 {set ::done 1}
after#1
% # start the event loop
% vwait ::done
hey! I caught an error: this is an error
% # this prompt appears after 40 seconds or so
You can also register your own error handler, as described in the documentation for "interp bgerror". This came along in tcl 8.5, though it had a bug that wasn't fixed until 8.5.3.
For example:
% set foo [interp create]
interp0
% $foo eval {proc myErrorHandler {args} {puts "myErrorHandler: $args"}}
% $foo bgerror myErrorHandler
myErrorHandler
% # after 30 seconds, throw an error
% $foo eval {after 30000 {error "this is an error"}}
after#0
% # after 40 seconds, terminate the loop
% $foo eval {after 40000 {set ::done 1}}
after#1
% $foo eval {vwait ::done}
myErrorHandler: {this is an error} {-code 1 -level 0 -errorcode NONE -errorinfo {this is an error
while executing
"error "this is an error""
("after" script)} -errorline 1}
% # this prompt appears after 40 seconds or so
Does this help answer your question?
If I've understood correctly what you want to do, your code should look similar to that:
set httpd_id [thread::create -preserved]
thread::send $http_id "source [file join $starkit::topdir bin/httpd.tcl]"
In this way you'll have TclHttpd running in a thread, without worrying for the vwait problem
If you also want to be informed about any error during the httpd execution, TclHttp sends all the errors to a log file. You can configure the path of the Log doing:
Log_SetFile "/logs/httpd_log"
You need to have the httpd::log package.
I hope this helps.