How to run repeatedly a proc in Tcl - tcl

I have written a proc in tcl which takes one argument (a positive integer) und displays a text. Lets call it permu (for permutation). I would like to execute this proc permanently, so
puts [permu 3]
and with the same argument (here 3), lets say every 2 or 3 seconds or so, without removing the previous outcome of the code. How can I do this?
The second question: Same question as above but I would like to clear the screen when the new outcome of permu is displayed.
Third question: In case that I decide to stop a running code (I work with Linux), for example the one above, how can I do this?
Thanks in advance!

Here's one way to do the repeated output:
proc repeat_permu {arg delay} {
puts [permu $arg]
after $delay [list repeat_permu $arg $delay]
}
# Note that the delay is in milliseconds
repeat_permu 3 3000
# Now start the event loop
vwait forever
To clear the screen you need to send the appropriate escape sequence before each new output. Most terminal emulators will accept the vt100 code, so you would do puts "\x1b[2J".
Normally you could just stop your program running by typing control-c, or do you want some means of doing this programmatically?
Update: A simpler way to do the repetition if you don't need to process any other events in parallel is just: while 1 {puts [permu 3]; after 3000}

Related

Periodically send command with Tcl

I am writing a Tcl/Tk interface to communicate with a temperature controller over Modbus. I would like to periodically (ie. every 5-10s or so) send a query to the temperature controller to get the current temperature.
Right now, I'm doing this by:
while 1 {
get_current_temperature;
after 5000;
}
This works, but it prevents the user from interacting with the GUI for the duration of the after command, which is very undesirable. Could anyone suggest a workaround for this?
Thank you for your help!
You are looking for the every proc: https://wiki.tcl-lang.org/page/every
proc every {ms body} {
after $ms [namespace code [info level 0]]
uplevel #0 $body
}
every 5000 get_current_temperature
The every command reschedules itself after the specified number of milliseconds, then runs the command. This is the other way around from most of the versions on the wiki page. It has the advantage(?) that it isn't affected by the time the command takes to run. It also doesn't stop the next iteration if the command throws an error.

Evaluate variables at delayed command with after

I have process were variables are defined, and following that procedure the variables should be used after a delay.
The problem is that the delayed command process the variables when the command is executed instead of when the command is given. Consider the following example:
The code is not tested, but the point should be clear anyway:
for {set i 0} {$i < 100} {incr i} {
set outputItem $i
set time [expr 1000+100*$i]
after $time {puts "Output was $outputItem"}
}
Which I would hope print something like:
Output was 1
Output was 2
Output was 3
...
But actually it prints:
Output was 100
Output was 100
Output was 100
Which I guess shows that tcl keeps the parameter name (and not the value of the variable) when the after command is initiated.
Is there any way to substitute the variable name to the variable content, so that the delayed command (after xxx yyy) works as desired?
The problem is this line:
after $time {puts "Output was $outputItem"}
The substitution of $outputItem is happening when the after event fires, not at the time you defined it. (The braces prevent anything else.) To get what you want, you need list quoting, and that's done with the list command:
after $time [list puts "Output was $outputItem"]
The list command builds lists… and pre-substituted commands (because of the way Tcl's syntax is defined). It's great for building things that you're going to call later. I guess it could have been called make-me-a-callback too, but then people would have wondered about its use for creating lists. It does both.
If your callback needs to be two or more commands, use a helper procedure (or an apply) to wrap it up into a single command; the reduction in confusion at trying to make callbacks work with multiple direct commands is totally worth it.

Button with multiple command line arguments

Can a button in tcl could be linked to multiple command line arguments ?
I have a code which runs when a button is clicked, a progressbar code with time in seconds is also linked with it , and should start at same time when this button is pressed.
I put both procs as a command in button command argument using {} but it fails with Error.
Code Snippet
button .b -image $p -command {progressbar 300 run_structural_comparision}
proc progressbar {seconds} {
ttk::progressbar .pg -orient horizontal -mode determinate -maximum $seconds
pack .pg -side left
update idletasks
# Do some real work here for $seconds seconds
for {set i 0} {$i < $seconds} {incr i} {
after 1000; # Just waiting in this example, might as well do something useful here
.pg step; # After some part of the work, advance the progressbar
update idletasks; # Needed to update the progressbar
}
# Done, clean up the dialog and progressbar
}
proc run_structural_comparision {} {
type_run
global ENTRYfilename ENTRYfilename2 curDIR curDIR2 typep reflib compLib rundir hvt_verilog logfile
set path [concat $reflib $compLib]
## set path [concat $ENTRYfilename $ENTRYfilename2]
puts $path
set str "compare_structure -overlap_when -type {timing constraint} -report compare_structure_"
set trt ".txt"
set structure [concat [string trim $str][string trim $typep][string trim $trt] $path]
puts $structure
puts $rundir
cd $rundir
set filename [concat "compare_structure_" $typep ".tcl"]
if {[ file exists $rundir/$filename] == 1 } {
exec rm -rf $rundir/compare_structure_$typep.tcl
}
A button's -command callback is a Tcl script. It will be evaluated at the global level of the stack. If you want to run two commands, you can just put a script in there to run the two commands:
button .b -command { command_1; command_2 }
This will run them sequentially. Tcl is naturally single-threaded as that is by far the easiest programming model for people to work with. It's possible to make code that works by doing callbacks to appear to be doing multiple things at once (that's how Tk works, just like virtually all other GUIs on the planet) but you're really only doing one thing at a time.
But your real question…
The core of what you need is a way to run the program that takes a long time in the background so that you can monitor it and continue to update the GUI. This is not a trivial topic, unfortunately, and the right answer will depend on exactly what is going on.
One of the simplest techniques is where the CPU-bound processing is done in a subprocess. In that case, you can run the subprocess via a pipeline and set fileevent to give you a notification callback when output is produced or the program terminates. Tcl is very good at this sort of thing; things that many languages have as very advanced techniques just feel natural when done with Tcl, as a great deal of thought has been put into how to make I/O callbacks work nicely.
If it's in-process and long-running without the opportunity for callbacks, things get more complex as you have to have the processing and the GUI updates in different threads. Which isn't too hard if you've got everything set up right, but which might require substantial re-architecting of your program (as it is usual for threads in Tcl to be extremely strongly partitioned from each other).
The simplest thing to do is to create a procedure that calls the two functions. If you wantie:
proc on_button_press {seconds} {
after idle [list progressbar $seconds]
after idle [list run_structural_comparision]
}
You can put multiple calls in the immediate button handler command string but it quickly gets complicated. But in short, use a semicolon to separate the two commands.
Your use if update idletasks should be considered a "code smell". ie: avoid it. In this case, in the progressbar function, setup the bar then just have everything else called by after calls to update the state of the progress.
I suspect your rm -rf may not do what you want. It it likely to lockup the interface as you get nothing back until the command has completed. Better is to write a function to walk the directory tree and delete the files with file delete and you can then raise progress events as you go and keep the UI alive by breaking up the processing into chunks using after again.

TCL command wrapping

I am trying to write a TCL proc that will allow me to wrap statements and then 'execute' them internally.
For example, if I originally have :
set var $tmp
I want to have some procedure :
proc wrapper {command} {
puts "do something here"
puts $command #ie below : write "set var $tmp" to stdout
EXECUTE COMMAND HERE
puts "do something else here"
}
set void [wrapper {set var $tmp}]
My motivation is that I'm trying to write my own timing/profiler for individual statements.
Instead of "EXECUTE COMMAND", use
uplevel 1 $command
Note that if you attempt to time a script using a command like your wrapper, it won't be byte-compiled, which means that it won't perform the way it would inside a procedure.
Documentation: uplevel
You can give a try with time command which is already available with Tcl if your only intention is to just get the time taken by a set of code to execute.
time script ?count?
This command will call the Tcl interpreter count times to evaluate script (or once if count is not specified). It will then return a string of the form
503.2 microseconds per iteration
which indicates the average amount of time required per iteration, in microseconds. Time is measured in elapsed time, not CPU time.
Example :
set code_block {
foreach x "1 2 3" {
set y [expr {$x*20}];
}
}
puts [time $code_block 10]; # Executing the code for 10 times
which might produce random outputs such as
11.9 microseconds per iteration
Update 1 :
If you want to print the commands along with execution which is also possible. You can override the time command with rename command.
rename time _time; # Changing the 'time' to '_time'
# Defining your own 'time' command
proc time {command {count 1}} {
# Printing the commands here
puts $command
# Calling the actual '_time' command
# and returning that value from here
return [_time $command $count]
}
Place the above code at the top of your code and usage of time command after this will be using our customized procedure only.
They are passed as set of command which in turn, yes, a string. But, while the evaluation of the code, it will behave as if like how it will work in a tclsh.
As answered by Mr.Peter, if your code involves accessing the previous level of commands, variables, then you have to use uplevel or upvar based on your needs.
Reference : time, rename

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.