Tcl is a fairly easy-to-learn script language, but there are a few peculiarities that might be confusing for beginners as well as for advanced users. This page explains its behavior; it is not meant as an introduction. We assume that you already have some basic knowledge about Tcl.
1. Tcl is a command language This means that the first token is always interpreted as a command or procedure. As a consequence, Tcl cannot cope with assignments such as set a [expr $b+$c] Another consequence is, that — when applying the rules as strictly as Tcl does — control structures ( # this a some comment \ this is some additional comment is the same as: # this a some comment # this is some additional comment Another implication is, that placing comment blocks in a script is more restrictive than in C: string first "a" $str # find "a" in string str ==> wrong # args See Section 7 and Section 8 for more details. 2. Why are control structures interpeted as commands?This is related to how Tcl scripts are evaluated: Step 1: The parser starts by checking the script for unmatched quotation marks and brackets, and splits the script in groups of characters. These groups can be:
Step 2: The commands are identified and are demarcated. The beginning of a command starts on a new line, or after a semicolon indicating the end of the previous command. Step 3: Before executing a command, all the arguments are pre-processed: the variables, backslashes, commands and set x {{a $b \n} c} ==> {a $b \n} c # x is a fixed string. all characters are left unchanged set x "{a $b \n} c" ==> can't read "b": no such variable set x "a{b" ==> a{b # no escape character required set x "a[b" ==> (waiting for a close bracket: "[" is read as the beginning of a command) An unmatched curly bracket in a fixed sting is ambiguous, because it coud be interpreted a a single character or as a delimiter. If it is intended as a literal bracket, then it must be preceded by a backslash. The same is true for quote characters in a text string. The main difference between both types of strings is that the escape characters in a text string are automatically interpreted, whereas in fixed strings you must use the Step 4: The commands are executed. "puts [lrange $x 1 end]" # this is the input string puts [lrange $x 1 end] # 1. splits the string into two arguments # 2. interprets the string as a single command puts [lrange {1 2 3 4} 1 end] # 3. substitutes variable x puts {2 3 4} # 4. executes lrange ==> 2 3 4 # 4. executes puts Steps 1-3 are executed without any knowledge of the commands. This mechanism has the advantage that the command name can also be a variable, or a user-defined procedure not yet available during compilation. Suppose, for example, that we have a variable that is used as a command name. If there is an escape character at the end of the line, this line and the next one are concatenated, independent of the command itself. So inevitably, both lines are always evaluated as a single command: set cmd "list" $cmd arguments of $cmd\ continue on next line ==> arguments of list continue on next line Now, if we set set cmd "#" $cmd arguments of $cmd\ continue on next line ==> (empty line) It is not recommended to apply these constructions arbitrarily, but they are essential to Tk and other Tcl extentions in which the widgets are dynamically create and their names are stored in Tcl variables. Widget names are nothing else but Tcl command of a special kind. Remarks subst {$a b} ==> can't read "a": no such variable If the same input would be passed as a text string, the error message is caused by the parser at step 3, but with the same results: subst "$a b" ==> can't read "a": no such variable Expressions set opt "HIDE" set show [expr $opt eq "SHOW" ] This looks OK, but you will get the following error message: invalid bareword "HIDE" in expression "HIDE eq SHOW"; should be "$HIDE" or "{HIDE}" or "HIDE(...)" or ... (parsing expression "HIDE eq SHOW") By definition, there is nothing wrong with the syntax, but what does go wrong is the way the equation is resolved. First, the parser substitutes expr HIDE eq SHOW So, if you want to keep it safe, use curly brackets to guarantee that the expression is passed to set show [expr {$opt eq "SHOW"} ] There is no need to place quotation marks around Bindings bind $path <1> { set pos [%W index @%x,%y] # pos becomes the new global variable ::pos } If you want to use local variables in the script, you can place quotation marks or the set path .f bind $path.c <1> "foo $path" stores the binding script as: " 3. eval and {*} If you want to execute a command that is stored in a string, you can use eval cmd -opt $value $arguments
eval cmd -opt [list $value] $arguments or the entire command as: eval [list cmd -opt $value $arguments] What happens is that cmd -opt $value {*}$arguments
set str "strerr test" puts $str ==> stderr test eval puts $str ==> test or use puts {*}$str ==> test The 4. Can I use arbitrary variable names? • A variable name can be an arbitrary string of characters. Names such as " • You can concatenate variables by writing them without whitespaces in between: set y $var append y "str" or: append y $var "str"; # (assumes that y is empty or does yet not exist) This is the same as writing: set var 0 set y $var" 1 2 3" ==> wrong # args: should be "set varName ?newValue?" set y $var"\ 1\ 2\ 3" ==> 0" 1 2 3" # quote characters are still present set y $var\ 1\ 2\ 3 ==> 0 1 2 3 # OK set y $var" ==> 0" As a matter of course, using 5. The differences between "", {} and () Brackets, set v {a\ b} ==> a b Backslash substitution is the same for text strings and fixed strings: set v "a\ b" ==> a b If you do not use a backslash, all character between the quotation marks or braces — including the newlines — are retained: string length {a\ b} ==> 3 string length {a b} ==> 10 # this includes the newline Hence, a backslash at the end of a line has a different meaning as in C (see below). In C, a backslash is used to indicate that the string continues on the next line; the leading spaces on the new line are retained. And, secondly, a backslash in C is mandatory whereas in Tcl, a string can cover several lines without special notice. Note that curly brackets inside quotation marks do not inhibit variable subtitution: set v "hello" set v2 {$v} ==> $v set v2 "{$v}" ==> {hello} string length {$v2} ==> 3 string length "{$v2}" ==> 7 The reason is that substitution takes place before the In most cases, quotation marks around a single word can be left out. But there are two exceptions. The first one are strings in expressions. The second case is if a word contains a semicolon. For example, append var v1 ; v2 gives an error, because the semicolon is interpreted as a command separator and append var "v1" ";" "v2" Curly brackets cannot be used to define a string containing a single curly bracket: set x {\{} ==> \{ So, if you want to locate a bracket in a string, you must write: If you do not insert a backslash, the character is read as an unmatched brace, but if you do so, the backslash is retained and you cannot get rid of it. This contradiction can only be solved afterwards, by using the set x {a\{b} => a\{b subst $x ==> a{b
set x {a\{b $c} ==> a\{b $c subst $x ==> can't read "c": no such variable subst -novariable $x ==> a{b $c A literal square bracket in a fixed string does not need to be preceeded by a backslash. Note, however, that if a string contains an unmatched square bracket that is not preceded by a backslash and you want to apply set c "something" set x {a[b $c} ==> a[b $c subst $x ==> missing close-bracket subst -nocommand $x ==> a[b something 6. Using a " \" at the end of a lineThere are three cases where backslashes to escape a newline is meaningful. • The first one is already mentioned above: it can be used to remove redundant leading spaces in a string covering more than a single line. Note that the whitespaces in front of the backslash are not affected. A backslash does not require a delimiter in font: set x {a \ b} ==> a b (two spaces in between. not just one) If you only want to get rid of the leading spaces, but still want to retain the newlines, you can write: set txt "line1\n\ line2\n\ line3" • The second case in which you need to write backslashes is, when you want to continue a command on the next line: command arg1 arg2 \ arg3 arg4 ... Without a preceding backslash, array set test [list \ index1 val1 \ index2 $val2 \ index3 "$val3 $val4" \ ] The backslashes are needed, not to group the text between the square brackets — brackets are always matched automatically — but to indicate that the array set test [ list \ index1 val1 \ index2 $val2 \ index3 "$val3 $val4" ] The best choice is to swap the quotation marks and the array set test " index1 val1 index2 $val2 index3 [list $val3 $val4] " • The third case is a trick to let Tcl scripts behave differently under bash and in Tcl: #!/bin/sh # The following line is hidden in Tcl, but not in bash \ exec wish $0 ${1+"$@"} # Tcl commands follow If you source this script in wish, In all other cases, a continuation token is redundant. You may read, for example, constructions such as: package ifneeded tdbc 1.0.6 \ "package require TclOO 0.6-;\ [list load [file join $dir tdbc106t.dll]]\;\ [list source [file join $dir tdbc.tcl]]" The first backslash is required to indicate that the subsequent lines are part of the package ifneeded tdbc 1.0.6 [subst { package require TclOO 0.6- load "[file join $dir tdbc106t.dll]" source "[file join $dir tdbc.tcl]" } ] Note the additional whitespace between 7. Control statements are evaluated as Tcl commandsInterpreting control statements as commands has a few consequences. • Conditions and arguments need to be placed in curly brackets instead of parentheses as in other languages. • Sometimes you can see: proc fnc x {...}; # no curly brackets around the argument instead of: proc fnc {x} {...} This is correct, but it is a sloppy style of programming and is not recommended because it makes the code less readable. Moreover, leaving out curly brackets may not always be correct, as we saw in the if $x { # do something } but for different reasons: set i 10 while [incr i -1] { puts $i } The switch {$x} { # compares the string "$x" and not its value # switches } • Why can't we just write: if {$a == str} {...} instead of: if {$a == "str"} {...} It is because the condition is passed as a fixed string to the # x contains an integer if {$x-5} { puts {$x does not equal 5} } else { puts {$x equals 5} } Similarly, instead of: set j 1 for {set i 10} {$j != 0} {incr i -1} { puts "$i $j" set j $i } we could also write: for {set i 10; set j 1} {$j} {set j $i; incr i -1} { puts "$i $j" } The first and third argument are evaluated as a code block, the second argument is evaluated as an expression. All variables in expressions, as well as the output of commands and procedures, are automatically interpreted either as a string or a number. Placing additional quotation marks around variables and commands are redundant. Quotation marks or curly brackets are needed to indicate that a group of characters must be interpreted as a string. This is also the case for macros ( bind $path <Event> { if {"[foo %W]" == ".widget"} { # redundant quotation marks around [] # no quotation marks needed around %W do something } if {%W == ".widget"} { # syntax error. %W requires quotation marks do something } } 8. Where to place comment lines• Positioning comment lines is more restrictive than in C. It might help to realize that comments are just special Tcl commands. You wouldn't write: string first "a" $str set str "test" then why writing: string first "a" $str # set str "test" ==> wrong # args: should be "string first needleString haystackString ?startIndex?" The comment line is parsed as the arguments of Generally, you can always start a comment string on a new line. This can be anywhere in a script, but not inside an argument list or an argument, except in the body of a statement. This also includes the switch statement: switch $n { # comment here is ok # comment here is also ok 1 {... commands ...} # you can comment out a case option # 2 {... commands ...} } • The parser checks whether the command is complete and this includes a comment block. As a result, commenting out a control statement as follows goes wrong: # while {$x} { (do something) # } ==> unmatched open brace in list You can add a backslash in front of the unmatched open braces in the comment lines. An unmatched close brace does not cause any problem. 9. Lists A list is not an intrinsic property of a string; it is just another interpretation. This interpretation is made by the command, and not by the parser. For example, lindex {{$a b} {c d}} 0; # interprets argument 1 as a list ==> a $b but: split {{$a b} {c d}}; # interprets the same argument as a string ==> \{\$a b\} \{c d\}; # list of four elements • A list can be created by parts as an ordinary string. The interpretation of the string comes afterwards: set lst {"a $b"} append lst " \{" append lst "c \$d\}" puts $lst ==> "a $b" {c $d} # we can read lst as an ordinary list: lindex $lst 0 ==> a $b lindex $lst 1 ==> c $d • The lists {"a $b" {c $d}} and {{a $b} {c $d}} are identical. However, quotation marks and braces in lists have a slightly different meaning. Although variables and commands are retained, backslash substitution is applied. So there is no difference between: if {[lindex {"$x" y} 0] == {$x}} { puts "identical" } else { puts "not identical" } and if {[lindex {{$x} y} 0] == {$x}} { puts "identical" } else { puts "not identical" } (In both cases, the left-hand value is a literal if {[lindex {"\n" y} 0] == "\n"} { puts "identical" } else { puts "not identical" } and if {[lindex {{\n} y} 0] == "\n"} { puts "identical" } else { puts "not identical" } are different. • The lists {{a b} {c d}} and {{a b} {c d}} are identical, only as far as the base elements, [lindex {{a b} {c d}} 1 1] == [lindex {{a b} {c d}} 1 1] but [lindex {{a b} {c d}} 1] != [lindex {{a b} {c d}} 1] This is because the output of • In addition to quotation marks and curly brackets, you can also group the elements by using backslashes in front of the whitespaces in the elements. The string list "a { b" ==> a\ \{\ b this, in contrast to: list "a b c" ==> {a b c} The reason is that curly brackets cannot be used to demarcate fixed strings containing unmatched open braces (see Section 5). The backslashes can be seen as a necessary internal representation. If you stick to the list commands, you might not even notice them at all: lindex [list "a { b" "c d" ] 0; # same list as above ==> a { b • Not all strings can be interpreted as lists. A list requires that the string does not contain unmatched braces or quotation marks. subst {\[ \{ (} ==> [ { ( the output cannot be interpreted as a list. Note that " lindex {\[ \{ (} 0 ==> [ lindex [subst {\[ \{ (} ] 0 ==> unmatched open brace in list Example if {[regexp {\s*proc } $line]} { # check the lines starting with "proc" # the next command will fail because $line is not complete: lappend procs [lindex $line 1] } You can check completeness with • If a variables is substituted then the replacement will still be interpreted as a single group of characters. So, set str "Hello World" bind $path <1> "puts $str" will evoke an error. The reason is that the binding script is evaluated during runtime, whereas substitution takes place in an earlier stage during compilation. Consequently, the executable script will read: " bind .b <1> [list puts $str] The bind Tree <FocusIn> { after idle BWidget::refocus %W %W.c } instead of: bind Tree <FocusIn> [list after idle {BWidget::refocus %W %W.c}] • Generally, quotes do not preserve list elements. For example: set var1 "a b" set var2 "c d" set lst "$var1 $var2" ==> a b c d # four elements set lst [list $var1 $var2] ==> {a b} {c d} Hence, • Tcl contains a set of commands, specially designed to process lists. They are: lappend lassign lindex linsert llength lmap lrange lreplace lreverse lsearch lset lsort The first argument of these commands always represents a list.
set lst2 [lappend lst1 elem] # lst1 and lst2 are identical. lst1 is also modified The list commands reformat input string to a list according to the standard form using curly brackets. The spaces in between the elements are replaced by a single character, but all elements remain untouched: set lst {"a $b" {c $d } } lappend lst "e f" ==> {a $b} {c $d } {e f} So, • There are four Tcl commands that create new lists. They are: 10. Namespaces A namespace is created as • Once a namespace is created, you can dynamically add and delete as many procedures and variables as needed. This can be done in another namespace eval ns { # the commands are executed inside a namespace variable x 10; # creates ns::x set y 0; # creates ns::y proc fnc {} { # creates ns::fnc puts "1 creates fnc2 in namespace [namespace current]" proc fnc2 {} { # this is ::ns::fnc2 set frameinfo [info frame [info frame]] puts "2 runs: [dict get $frameinfo proc]" namespace eval :: { puts " evaluates script in global namespace\n \ creates fnc2 in namespace [namespace current]" proc fnc2 {} { # this is ::fnc2 set frameinfo [info frame [info frame]] puts "3 runs: [dict get $frameinfo proc]" } } } } namespace eval ns2 {puts [list creates namespace [namespace current]]} puts "Calling ns::fnc" fnc; # executes ns::fnc; creates ns::fnc2 puts "Calling ns::fnc2" fnc2; # executes ns2::fnc2; creates ::fnc2 puts "Calling ::fnc2" ::fnc2; # executes ::fnc2 } and: # all commands are executed in the global namespace. This only shows the framework namespace eval ns {} set ns::x 10 set ns::y 0 proc ns::fnc {} { puts "proc ::ns::fnc" proc fnc2 {} {puts "proc ::ns::fnc2"} namespace eval :: { proc fnc2 {} {puts "proc ::fnc2"} } } namespace eval ns::ns2 {puts "namespace ::ns::ns2"} ns::fnc ns::fnc2 fnc2 • All procedures and variables that are created by • The namespace eval ns {variable var} info vars ns::* ==> ::ns::var # i.e. ns::var exists set ns::var ==> can't read "ns::var": no such variable unset ns::var ==> can't unset "ns::var": no such variable • Global variables and procedures are stored in the global namespace. Its name is the empty string. Write • An callback event is always executed in the global namespace. If you create a variable in a binding, it will be retained as a global variable. Moreover, you can access global variables without adding set ::globvar 0 bind $w <Button-1> { incr globvar puts " \$globvar and \$::globvar are the same variables: $globvar $::globvar" } 11. Stack levels If you enter a procedure you will go to the next stack level. This is also the case inside a " uplevel uplevel 1 [list catch [lreplace $args 0 0 [lindex $cmds $n]] ::result ::opt] (meaning: replace the command argument in uplevel catch [list [lreplace $args 0 0 [lindex $cmds $n]]] ::result ::opt Note that the set cmd [lreplace $args 0 0 [lindex $cmds $n]] uplevel catch [list $cmd] ::result ::opt This is due to how concatenation in Tcl is defined: set var val1; set lst {val2 val3} concat $var $lst ==> val1 val2 val3 concat $var [list $lst] ==> val1 {val2 val3} upvar proc copy {var1 var2} { upvar $var2 lvar2 set lvar2 $var1 } set var1 10 copy $var1 var2 set var2 ==> 10
proc copy {var1 var2} { upvar var2 lvar2 set lvar2 $var1 } set var1 10 copy $var1 var2 set var2 ==> 10 Seemingly with the same results, but the argument 12. Tcl/Tk and IPEnv IPEnv is a multilingual IDE, but it has strong roots in Tcl/Tk. If you are programming in Tcl/Tk, you can profit from all the advantages. that IPEnv has to offer: sophisticated automatic code completion, clever goto and find functions and it recognizes Tcl procedure prototypes. You can load your own plug-ins written in Tcl/Tk.
Just give it a try and you will realize what you are missing. |