How we create sink(5) variable in tcl script - tcl

i am using 20 node in ns2 , i am trying to access the a(0) a(1) a(2) a(3) a(4) variables with for loop how can i do that
here is my code
for {set i 0} {$i < $val(nn)} {incr i} {
set sink($i) [new Agent/LossMonitor]
$ns attach-agent $n($i) $sink($i)
}
but its gives an error
bad variable name "sink(0)": upvar won't create a scalar variable that looks like an array element
i declare the variable in following manner
proc record {} {
global sink(0) sink(1) sink(2) sink(3) sink(4) sink(5)
}

Just use
global sink
(global is just a special upvar case)
variables that end with (...) are (associative) arrays, using numbers as key is not recommended, it is better to use a list instead, e.g.
set mylist {}
# append some elements
lappend mylist "foo" "bar" "baz"
# get the 2nd element
puts [lindex $mylist 1]
# set the 3rd element to "Hello World"
lset mylist 2 "Hello World"

Related

Getting the next key in an array

My goal is to find the next key in an array... below my data :
# Index increment may change, there is not necessarily continuity like this example.
# My $index can be 1,2,3,4,8,12,25,32...
# but the size of my array is about 100,000 elements.
for {set index 1} {$index < 100000} {incr index} {
set refdata($index,Pt,X) [expr {10 + $index}]
}
I need to know the next key to be able to build a geometric line... I did not find in the help a command that allows me to find the next key of my array so I created my own function below :
proc SearchNextKeyArrayElement {dataarray mykey} {
upvar $dataarray myarray
set mydata [lsort -dictionary [array names myarray]]
set index [lsearch $mydata $mykey]
if {$index > -1} {
return [lindex $mydata [expr {$index + 1}]]
}
return ""
}
foreach k [lsort -dictionary [array names refdata]] {
if {[string match "*,Pt,*" $k]} {
set nextkey [SearchNextKeyArrayElement refdata $k]
}
}
And it takes a long time...array nextelement command is maybe the solution...But I do not understand how to use it ?
Here's an example:
start a search with array startsearch
loop while array anymore is true
get the next key with array nextelement
tidy up with array donesearch
use try {} catch {} finally for safety
# array foreach
# to be subsumed in Tcl 8.7 by `array for`
# https://core.tcl.tk/tips/doc/trunk/tip/421.md
#
# example:
# array set A {foo bar baz qux}
# array foreach {key val} A {puts "name=$key, value=$val"}
#
# A note on performance: we're not saving any time with this approach.
# This is essentially `foreach name [array names ary] {...}
# We are saving memory: iterating over the names versus extracting
# them all at the beginning.
#
proc array_foreach {vars arrayName body} {
if {[llength $vars] != 2} {
error {array foreach: "vars" must be a 2 element list}
}
lassign $vars keyVar valueVar
# Using the complicated `upvar 1 $arrayName $arrayName` so that any
# error messages propagate up with the user's array name
upvar 1 $arrayName $arrayName \
$keyVar key \
$valueVar value
set sid [array startsearch $arrayName]
# If the array is modified while a search is ongoing, the searchID will
# be invalidated: wrap the commands that use $sid in a try block.
try {
while {[array anymore $arrayName $sid]} {
set key [array nextelement $arrayName $sid]
set value [set "${arrayName}($key)"]
uplevel 1 $body
}
} trap {TCL LOOKUP ARRAYSEARCH} {"" e} {
puts stderr [list $e]
dict set e -errorinfo "detected attempt to add/delete array keys while iterating"
return -options $e
} finally {
array donesearch $arrayName $sid
}
return
}
Generally speaking, Tcl arrays have no order at all; they can change their order on any modification to the array or any of its elements. The commands that iterate over the array (array for, array get, array names, and the iteration commands array startsearch/array nextelement/array anymore) only work with the current order. However, you can use array names to get the element names into a Tcl list (which is order preserving), sort those to get the order that you're going to iterate over, and then use foreach over that. As long as you're not adding or removing elements, it'll be fine. (Adding elements is sort-of OK too; you'll just not see them in your iteration.)
foreach key [lsort -dictionary [array names myarray]] {
ProcessElement $key $myarray($key)
}
By contrast, trying to just go from one element to the next will hurt a lot; that operation is not exposed.
Using the iteration commands is done like this:
set s [array startsearch myarray]
while {[array anymore myarray $s]} {
set key [array nextelement myarray $s]
ProcessElement $key $myarray($key)
}
Note that you don't get an option to sort the search. You won't see these used much in production code; doing array names or array get is usually better. And now (well, 8.7 is still in alpha) you've also got array for:
array for {key value} myarray {
ProcessElement $key $value
}
Efficient for large arrays, but still doesn't permit sorting; supporting direct sorting would require a different sort of storage engine on the back of the array.
This is why it's slow: You're sorting the array names once for the foreach command and then again for each element. Sort once and cache it, then you can iterate over it much more efficiently
set sorted_names [lsort -dictionary [array names refdata -glob {*,Pt,*}]]
set len [llength $sorted_names]
for {set i 0; set j 1} {$i < $len} {incr i; incr j} {
set this_name [lindex $sorted_names $i]
set next_name [lindex $sorted_names $j]
# ...
}

how i can insert item to list in tcl from user input

Hi i'm new to tcl i'm trying to insert element to list in proc from user input and return the list and invoke it in another list
i have tried this and i'm get
puts "Enter list Size"
set size [gets stdin]
set aList [fillTheList $size]
proc fillTheList {arg1 } {
set lList {}
for {set i 0} {$i <= $arg1} {incr i} {
set value [gets stdin]
linsert $lList $i int(value)]
puts "[lindex $lList $i]"
}
return $lList
}
and i'm getting this error in cmd
invalid command name "fillTheList"
while executing
"fillTheList $size"
invoked from within
"set aList [fillTheList $size]"
(file "ascending.tcl" line 5)
Try
proc fillTheList {arg1 } {
set lList {}
for {set i 0} {$i < $arg1} {incr i} {
puts -nonewline "Enter value "
set value [gets stdin]
lappend lList $value
puts [lindex $lList $i]
}
return $lList
}
puts -nonewline "Enter list Size "
set size [gets stdin]
set aList [fillTheList $size]
A couple of notes:
If you set the condition in the for invocation to $i <= $arg1 it will ask for one more list item than you wanted, since i starts from 0.
Instead of lappend, lset lList $i $value could be used. It used to only be able to change elements already in the list, but nowadays it can change the element after the last one in the list, extending the list by one.
lList is a really bad variable name, because it is easy to mix up with names like IList.
Tcl is barely typed at all. You type strings from the keyboard, those strings are entered in the list. If those strings are valid integers they can be used like integers. You don't need, and you can't, convert them.
Documentation:
< (operator),
for,
gets,
incr,
lappend,
lindex,
lset,
proc,
puts,
return,
set

execute tcl commands line by line

I have a file like this:
set position {0.50 0.50}
set visibility false
set text {ID: {entity.id}\n Value: {entity.contour_val}}
And I want to do something similar to source, but I want to use a file handle only.
My current attempt looks like this:
proc readArray {fileHandle arrayName} {
upvar $arrayName arr
set cl 0
while {! [eof $fileHandle]} {
set cl [expr "$cl + 1"]
set line [gets $fileHandle]
if [$line eq {}] continue
puts $line
namespace eval ::__esg_priv "
uplevel 1 {*}$line
"
info vars ::__esg_priv::*
foreach varPath [info vars ::__esg_priv::*] {
set varName [string map { ::__esg_priv:: "" } $varPath]
puts "Setting arr($varName) -> [set $varPath]"
set arr($varName) [set $varPath]
}
namespace delete __esg_priv
}
puts "$cl number of lines read"
}
In place of uplevel I tried many combinations of eval and quoting.
My problem is, it either fails on the lines with lists or it does not actuall set the variables.
What is the right way to do it, if the executed commands are expected to be any valid code.
An extra question would be how to properly apply error checking, which I haven't tried yet.
After a call to
readArray [open "myFile.tcl" r] arr
I expect that
parray arr
issues something like:
arr(position) = 0.50 0.50
arr(text) = ID: {entity.id}\n Value: {entity.contour_val}
arr(visibility) = false
BTW: The last line contains internal {}, which are supposed to make it into the string variables. And there is no intent to make this a dict.
This code works, but there are still some problems with it:
proc readArray {fileHandle arrayName} {
upvar $arrayName arr
set cl 0
while {! [eof $fileHandle]} {
incr cl ;# !
set line [gets $fileHandle]
if {$line eq {}} continue ;# !
puts $line
namespace eval ::__esg_priv $line ;# !
foreach varPath [info vars ::__esg_priv::*] {
set varName [string map { ::__esg_priv:: "" } $varPath]
puts "Setting arr($varName) -> [set $varPath]"
set arr($varName) [set $varPath]
}
namespace delete __esg_priv
}
puts "$cl number of lines read"
}
I've taken out a couple of lines that didn't seem necessary, and changed some lines a bit.
You don't need set cl [expr "$cl + 1"]: incr cl will do.
if [$line eq {}] continue will fail because the [...] is a command substitution. if {$line eq {}} continue (braces instead of brackets) does what you intend.
Unless you are accessing variables in another scope, you won't need uplevel. namespace eval ::__esg_priv $line will evaluate one line in the designated namespace.
I didn't change the following, but maybe you should:
set varName [string map { ::__esg_priv:: "" } $varPath] works as intended, but set varName [namespace tail $varPath] is cleaner.
Be aware that if there exists a global variable with the same name as one of the variables in your file, no namespace variable will be created; the global variable will be updated instead.
If you intend to use the value in the text variable as a dictionary, you need to remove either the \n or the braces.
According to your question title, you want to evaluate the file line by line. If that requirement can be lifted, your code could be simplified by reading the whole script in one operation and then evaluating it with a single namespace eval.
ETA
This solution is a lot more robust in that it reads the script in a sandbox (always a good idea when writing code that will execute arbitrary external code) and redefines (within that sandbox) the set command to create members in your array instead of regular variables.
proc readArray {fileHandle arrayName} {
upvar 1 $arrayName arr
set int [interp create -safe]
$int alias set apply {{name value} {
uplevel 1 [list set arr($name) $value]
}}
$int eval [read $fileHandle]
interp delete $int
}
To make it even more safe against unexpected interaction with global variables etc, look at the interp package in the Tcllib. It lets you create an interpreter that is completely empty.
Documentation: apply, continue, eof, foreach, gets, if, incr, info, interp package, interp, list, namespace, proc, puts, set, string, uplevel, upvar, while

get var name interpolated when evaluating splatter Tcl

This is a rather simple one to ask. Just to clarify, I am working Tcl 8.5.
I want to get variable interpolation done:
>set p "puts me"
>puts $p
puts me
>{*}$p
me
So this works fine. Now I want a string that says puts $varname to work, and that fails.
>set k {puts $p}
>{*}$k
$p
I want Tcl to output the value of var p. Using "evil eval" gets it done:
> eval $k
puts me
How can I get the eval behaviour w/o using eval?
Thanks.
Multi-word values
Need to subst each item in the list otherwise multi-word values would become parameters.
set p {What is Awesome?}
set k {puts $p}
set len [llength $k]
# Must subst each item in the list in order
# to execute k properly.
for {set i 0} {$i < $len} {incr i} {
lset k $i [subst [lindex $k $i]]
}
{*}$k
Output:
./puts.tcl
What is Awesome?
Single Word Values
Use subst to do variable substitution.
puts.tcl
#!/usr/bin/tclsh
set p Awesome
set k {puts $p}
{*}[subst $k]
output:
./puts.tcl
Awesome
You could do this with the proc and uplevel commands.
set me {yo yo}
set p {puts $me}
proc doit {} "uplevel 1 {$p}"
doit

Expanded TCL interpreter in TCL

I have implemented many TCL extensions for a specific tool in the domain of formal methods (extensions are implemented in C but I do not want solution to rely on this fact). Thus, the users of my tool can use TCL for prototyping algorithms. Many of them are just linear list of commands (they are powerfull), e.g.:
my_read_file f
my_do_something a b c
my_do_something_else a b c
Now, I am interested in timing. It is possible to change the script to get:
puts [time [my_read_file f] 1]
puts [time [my_do_something a b c] 1]
puts [time [my_do_something_else a b c] 1]
Instead of this I want to define procedure xsource that executes a TCL script and get/write timing for all my commands. Some kind of a profiler. I wrote a naive implementation where the main idea is as follows:
set f [open [lindex $argv 0] r]
set inputLine ""
while {[gets $f line] >= 0} {
set d [expr [string length $line] - 1]
if { $d >= 0 } {
if { [string index $line 0] != "#" } {
if {[string index $line $d] == "\\"} {
set inputLine "$inputLine [string trimright [string range $line 0 [expr $d - 1]]]"
} else {
set inputLine "$inputLine $line"
set inputLine [string trimleft $inputLine]
puts $inputLine
puts [time {eval $inputLine} 1]
}
set inputLine ""
}
}
}
It works for linear list of commands and even allows comments and commands over multiple lines. But it fails if the user uses if statements, loops, and definition of procedures. Can you propose a better approach? It must be pure TCL script with as few extensions as possible.
One way of doing what you're asking for is to use execution traces. Here's a script that can do just that:
package require Tcl 8.5
# The machinery for tracking command execution times; prints the time taken
# upon termination of the command. More info is available too (e.g., did the
# command have an exception) but isn't printed here.
variable timerStack {}
proc timerEnter {cmd op} {
variable timerStack
lappend timerStack [clock microseconds]
}
proc timerLeave {cmd code result op} {
variable timerStack
set now [clock microseconds]
set then [lindex $timerStack end]
set timerStack [lrange $timerStack 0 end-1]
# Remove this length check to print everything out; could be a lot!
# Alternatively, modify the comparison to print more stack frames.
if {[llength $timerStack] < 1} {
puts "[expr {$now-$then}]: $cmd"
}
}
# Add the magic!
trace add execution source enterstep timerEnter
trace add execution source leavestep timerLeave
# And invoke the magic, magically
source [set argv [lassign $argv argv0];set argv0]
# Alternatively, if you don't want argument rewriting, just do:
# source yourScript.tcl
Then you'd call it like this (assuming you've put it in a file called timer.tcl):
tclsh8.5 timer.tcl yourScript.tcl
Be aware that this script has a considerable amount of overhead, as it inhibits many optimization strategies that are normally used. That won't matter too much for uses where you're doing the real meat in your own C code, but when it's lots of loops in Tcl then you'll notice a lot.
You can wrap your commands which you want to measure. And name wrappers exactly as the original ones (renaming original procs before). After that, when instrumented command is executed it actually executes the wrapper, which executes the original procedure and measure the time of execution. The example below (Tcl 8.5).
proc instrument {procs} {
set skip_procs {proc rename instrument puts time subst uplevel return}
foreach p $procs {
if {$p ni $skip_procs} {
uplevel [subst -nocommands {
rename $p __$p
proc $p {args} {
puts "$p: [time {set r [__$p {*}\$args]}]"
return \$r
}
}]
}
}
}
proc my_proc {a} {
set r 1
for {set i 1} {$i <= $a} {incr i} {
set r [expr {$r * $i}]
}
return $r
}
proc my_another_proc {a b} {
set r 0
for {set i $a} {$i <= $b} {incr i} {
incr r $i
}
return $r
}
instrument [info commands my_*]
puts "100 = [my_proc 100]"
puts "200 = [my_proc 100]"
puts "100 - 200 = [my_another_proc 100 200]"
You might want to look at the command "info complete". It can tell you if what you have accumulated so far looks complete from the point of view of most common Tcl syntax markers. It will deal with command input that might be spread across multiple physical lines.