Search code examples
tclns2

Tcl can't read variable: no such variable


The following piece of code produces the error:

can't read "n": no such variable
    while executing
"$ns duplex-link $n$i $n([expr ($i+1)%120]) 1Mb 10ms DropTail"
    ("for" body line 2)
    invoked from within
"for {set i 7} {$i < 120} {incr i} {
      $ns duplex-link $n$i $n([expr ($i+1)%120]) 1Mb 10ms DropTail
}"
    (file "multicast.tcl" line 44)

it seems $n$i is not evaluated to the required format of $n7 etc. Any help in the solution is much appreciated.

for {set i 0} {$i < 120} {incr i} {
set n$i "[$ns node]"
global n$i
}

# Create links
$ns duplex-link $n0 $n1 1.5Mb 10ms DropTail
$ns duplex-link $n0 $n2 1.5Mb 10ms DropTail
$ns duplex-link $n2 $n3 1.5Mb 10ms DropTail
$ns duplex-link $n2 $n4 1.5Mb 10ms DropTail
$ns duplex-link $n1 $n7 1.5Mb 10ms DropTail
$ns duplex-link $n1 $n5 1.5Mb 10ms DropTail
$ns duplex-link $n4 $n6 1.5Mb 10ms DropTail

#create the rest of the links
for {set i 7} {$i < 120} {incr i} {
      $ns duplex-link $n$i $n([expr ($i+1)%120]) 1Mb 10ms DropTail
}

Solution

  • Tcl's $ syntax does not parse non-alphanumeric variable names (with a few exceptions I'll come to in a moment) so it stops trying to parse the first part of $n$i after the n. This is a limitation of the parser but Tcl itself allows almost anything in.

    One of those exceptions is that :: namespace separators are allowed too, and another is that you can put a complex literal variable name in {braces}, like ${n$i}. That's not helpful here though, as you can't substitute variables into variable names that way.

    What you should do

    Use an array. The form $somename(stuff-to-do-an-index) allows a full range of substitutions in the stuff-to-do-an-index, except for some restrictions on parentheses that hardly ever matter.

    global ni;  # <-- you might not need this!
    for {set i 0} {$i < 120} {incr i} {
        set ni($i) "[$ns node]"
    }
    
    # Create links
    $ns duplex-link $ni(0) $ni(1) 1.5Mb 10ms DropTail
    $ns duplex-link $ni(0) $ni(2) 1.5Mb 10ms DropTail
    $ns duplex-link $ni(2) $ni(3) 1.5Mb 10ms DropTail
    $ns duplex-link $ni(2) $ni(4) 1.5Mb 10ms DropTail
    $ns duplex-link $ni(1) $ni(7) 1.5Mb 10ms DropTail
    $ns duplex-link $ni(1) $ni(5) 1.5Mb 10ms DropTail
    $ns duplex-link $ni(4) $ni(6) 1.5Mb 10ms DropTail
    
    #create the rest of the links
    for {set i 7} {$i < 120} {incr i} {
        $ns duplex-link $ni($i) $n([expr ($i+1)%120]) 1Mb 10ms DropTail
    }
    

    Other alternatives

    You can use the one-argument version of set to read from variables (it's documented, but a little obscure to those new to Tcl).

    $ns duplex-link [set n$i] $n([expr ($i+1)%120]) 1Mb 10ms DropTail
    

    You can also use upvar 0 to make an alias to the variable which you can then manipulate normally:

    upvar 0 n$i myAlias
    $ns duplex-link $myAlias $n([expr ($i+1)%120]) 1Mb 10ms DropTail
    

    Even more ugly would be this construction with subst:

    $ns duplex-link [subst "\$n$i"] $n([expr ($i+1)%120]) 1Mb 10ms DropTail
    

    After that, it gets really nasty with eval and return -level 0 (which is actually efficient: strange but true!) and all sorts of stuff like that, but really don't go that way. Arrays are perfect for this sort of thing, really.