tcl tk scripting language

TCL = Tool cmd language

Tcl is similar to other shell lang. TCL can be scripting language, but it can also be used as shell as "tclsh".

TK can be used to develop gui i/f.
Tcl/Tk is open source, and can be included in C/C++ etc, or vice versa.

official tcl/tk documentation (very detailed): http://www.tcl.tk/doc/

As alwyas, tutorial point is very good source for learning anything: https://www.tutorialspoint.com/tcl-tk/tcl_tk_quick_guide.htm

TCL is a very weird language, interpretor written ver differently than how other high level lnguages are written in. Richard M Stallman has openly said that Tcl as a language should be completely avoided. There are weird cases, and language is more of learning by experience than learning by syntax. Same code written in 2 ways doing the same thing and syntactically correct, may be interpreted by the interpreter different and give different results. You should avoid using Tcl for any general purpose work, and use it only where it's absolutely needed for the job.

TCL is mostly used in CAD tools. If you are going to be using any hardware CAD programs from Synopsys, Cadence etc, they exclusively use tcl. So, if you have to learn it, whether you like it or hate it.

Tcl/Tk Installation:

tcl/tk may not be installed by default on most Linux OS. Here's the steps for downloading and nstalling tcl/tk:

On CentOS: With any software installation, there are 2 ways to install. Install it using package installer (which will automatically download and install all dependencies), or download it yourself and install it. The preferred way is using "yum" package installer on CentOS, but sometimes that package is not available via yum, so we may be forced to do manual process.

A. yum installation: sudo yum install tcl => This shows tcl.x86_64 as the pkg being installed. Click "Y" and it installs it in /usr/bin/tclsh (using cmd "which tclsh" shows it). For some reason, this wasn't working well with some other tool (missing files), so I installed it directly

B. direct installation: Download it from "https://www.tcl.tk/software/tcltk/download.html". I downloaded tcl8.7a1-src.tar.gz and tk8.7a1-src.tar.gz. After downloading it in some dir, goto that dir using gui file browser, right click and choose "extract". This will unzip and untar all files/dir and create new dir with same name "tcl8.7a1" and "tk8.7a1". Once this is done, goto unix terminal. 

1. cd tcl8.7a1/unix.

2. type "./configure"

3. type "make" => this will finish, with no "success" or "fail" info. Just some "gcc -O2 ..." cmds at the end.

4. type "make test" => runs all tests. Summary should show all passed (Total=31405, Passed=30101, skipped=1303, Failed=1= http.test). Many will be skipped. That's OK.

5. type "sudo make install" => installs it in "/usr/local/bin/tclsh8.7" All files are in /usr/local. Lib are in /usr/local/lib/tcl8.7/

6. If we run tclsh, it won't find anything. Even though "echo $PATH" shows /usr/local/bin in the path variable. Reason, tclsh is installed as tclsh8.7. We will fix it in step 8.

7. Now repeat same process for tk8.7a1. cd unix, ./configure, make, make test (Total=9544, Passed=8459, Skipped=996, Failed=89,Then again shows Total=465, Passed=448, skipped=17, failed=0), sudo make install. This gets installed as /usr/local/bin/wish8.7

 8. Now create soft links so that tclsh (which is actually tcl) and wish (which is actually tk) can run by typing tclsh or wish. cd /usr/local/bin. "ln -s tclsh8.7 tclsh" and "ln -s wish8.7 wish". Now all the tools which rely on tclsh or wish should be able to access it, as long as the path "/usr/local/bin" exists in PATH var. tclsh and wish are installed with their version numbers, so that different versions of the same lang can be installed at the same time without overwriting each other. That way if 2 different tools require 2 different versions of a pgm, they can run simultaneously by changing soft links or hardcoding the actual path of pgm with correct version number.

NOTE: when havving multiple versions of tcl/tk on your system, some files do not get installed correctly, resulting in "missing files" when calling an app that requires tcl/tk. Part of the confusion may be because some files are named same for for any version of tcl/tk installation resulting in 1 file getting used for mutiple versions, and depending on which is the last one that got over written, it may or may not work with other versions. Make sure you have these files:

> whereis tk
tk: /usr/lib64/tk8.5  /usr/include/tk.h /usr/share/tk8.5
> whereis tcl
tcl: /usr/lib64/tcl8.5 /usr/include/tcl.h /usr/share/tcl8.5

> whereis tclsh
tclsh: /usr/bin/tclsh /usr/bin/tclsh8.5 => tclsh is just a link to tclsh8.5

Depending on manual installation, these files might also be in /usr/local/bin/ and /usr/local/lib/ (since all software gets installed in /usr/local/ by default). Make sure, these files are there:

/usr/share/tcl8.5/ => has tclIndex, bunch of other tcl files and dir

/usr/lib64/  => has tclConfig.sh, libtcl8.5.so and tcl8.5 dir (this dir has tclConfig.sh which is just a soft link to tclConfig.sh in /usr/lib64/).

/usr/include/ => has tcl.h and bunch of tcl*.h files and dir


TCL syntax:

Any language has data and cmds (or operators) to act on data. Then there are special characters. All these are parsed by the interpreter to figure out what to do with that line to produce the result. We'll learn about data (var and their data types), cmd (reserved keywords and operators) and special characters (anything other than alphanumeric and operators).

tcl is case sensitive lang.

tcl script is a string consisting of 1 or more cmds. Tcl cmds are list of words, with first word being the cmd, and remaining words being args. This is totally different than how all other scripting languages or high level languages work. Whitespace separates multiple words of the cmd. To group multiple words into single arg, we use {} or "". To separate different cmds from each other, newline is used (so use separate line for each cmd). To put multiple cmds in same line, use semicolon ( ; ). So, newline and semicolon are very important in tcl (you need atleast 1 of them), else diff cmds may not be recognized.

NOTE: Since each cmd in tcl is on it's own line (or separated by ;), you can't put characters as {, }, ", etc anywhere. You have to follow strict syntax so that they get recognized as a group. So, tcl is very different than other languages where spaces, newline and positioning of characters matter. This is due to the fact that every string is treated as a cmd. So, if the string at the start of the line by itself doesn't seem to be a cmd, tcl will error out with "unkown cmd" (i.e having { by itself on a line is not a valid cmd). See more examples below with various cmds.

This link neatly explains how tcl interpreter interprets words: http://www.tcl.tk/man/tcl8.5/TclCmd/Tcl.htm

 

Tcl special characters: Just like all other languages, tcl has it's own set of special characters. They are mostly consistent with shell (bash, csh) special characters, but differ in some ways.

  • $ : dereferences a variable. $a refers to contents of var "a"
  • ( ) : Used for grouping expressions. This is used with arrays. It's not used in grouping cmds or words, as in "for" loop, etc. Curly braces { ... } explained below are used for all those cases. So, use of () is very limited in tcl.
  • [ ] : This does cmd substitution. Denotes a nested command. these evaluate cmds/scripts inside [ ]. The result is then put out and used by other cmds surrounding this. It means evaluate that string as a script immediately and use the result of the script as the value to substitute. It's used when calling user defined proc or other built in cmds.
  • \ : Used for escape quoting. Any char after \ is treated as ordinary char (loses it's special char meaning), and included in the word. Escape rule is similar to \ in bash, where it hides all special char from shell, but only 1 char at a time. The exception to this is the following special sequence => \a, \b, \t, \n, \\ ( this gets substituted as backslash \ ) , \xhh, \uhhhh,. So, \n is not escaped as tcl compiler sees the word "n" after "\" and omits escaping. However \\n will be seen as escape "\" followed by "\" and then a letter n, so final escaped value would be "\n". Escaping still happens within double quotes "..." but not in braces {...}. See next 2 bullets. 
    • If you put \ at end of line then since backslash escapes whatever comes after it, it will escape the newline. This looks similar to \\n, but here "\n" is one char (newline char), so that whole newline char is escaped. So, the shell doesn't see it as end of line. That means that next line will be treated as continuation of current line, and keeps on doing it for as many lines as you have \. All of these lines are then treated as one string.
    • VERY IMP: If you put space after a "\" at the end of current line, then \ escapes the space and not the newline. Escaping the space does nothing (since space is not a special char). Since newline is not escaped, the current line ends and new line starts from next line. This may break your code, as you may have intended for next line to be continuation of current line. Next line may not be valid cmd anymore and you may get "error". This is very common mistake in tcl, and since most editors don't show space explicitly, be extremely careful when using "\" at end of line. This is an issue in comment line starting with # too, see below for details.
  • " " : Denotes weak quoting; Command substitution, variable substitution, and backslash substitution are performed on the characters between the quotes. Groups multiple words into one. This is similar to  double quotes " " in bash, where only whitespace char are hidden from tclsh. It treats groups of words inside " " as 1 string.
    • ex: puts "..\n... \$a..." => Here newline char is not escaped (as it's excluded from escaping anyway(see escape quoting above). $ sign is escaped here even though it's inside quotes, as backslash escaping is still done inside quotes. So, we print $a instead of value of $a as $ got ecaped.
  • { } : Denotes rigid quoting; there are no substitutions. Groups multiple words into one. it means “don't do anything with this string inside { } at all; use it as-is. Sometimes you're feeding that string into a command that evaluates, so this helps do that, as nothing gets substituted. This is similar to single quotes ' ' in bash, where all special char are hidden from shell. The second most popular use of { } is to group words everywhere, i.e in loop body, conditional stmt, proc, etc. However, strictly speaking, they are not required at all except for places where we don't want substitution. We use them in proc args/body, but we can write proc entirely w/o using curly braces. curly braces within a comment are considered too when looking for start-end pair of {...}, so be very careful to not have single curly braces inside a comment (see below in comment section).
  • ; : Ends a command. needed when multiple cmds are used on same line
  • # :Begins a comment. Comments may be placed at start of line, or after end of cmd (which ends by having a ; at the end. Comments in Tcl are just not ignored, but are put in a "process later" bucket while the parser keeps moving forward. In tcl, you just can't take a piece of code, and comment it using "#" expecting it to run without errors. Syntax errors within "#" may still give errors. 
    • ex: set a 2; #my comment here => This works as a comment
    • if {0} { all comments } => #comment has many caveats as shown below, so one popular alternative to using # is to put comments in this "if 0 { ... }" construct (braces not necessary around 0). This always works without issues.
    • CAVEAT 1: braces {...} should always appear in pair in a comment, else it will give rise to mismatched { ... }. {...} in comments which are not in a pair will be errored out by the interpretor as not having matching braces. Also, the interpreter will point you to "error" in start of the program or in other weird places, when the error is due to mismatched { ... } in comments. It has no clue of where the mismatch happened, when it's nside a comment.
    • CAVEAT 2: comments in tcl are not just ignored, but are also looked for "\" at end of line. This is in contrast to bash/csh, where they are just ignored. So, if you comment out an existing piece of code which has \ at the end of line, your comment may not work. This is a pain, as comments can't be put blindly over a piece of code. One side benfit of this is that a comment can be extended to other line by using "\" at end of 1st comment line. This gives rise to some weird code as seen below:

ex: sometimes we see code where we execute a script2 within a shell script1, which sources the same original script1. This looks like a recursive infinite loop, but can work well using "\" at end of comment for tcl files. This script myscript.sh below is a bash shell script. Only 1st 3 lines are bash cmds, remaining are tcl cmds. On running, this script starts running as bash, based on 1st line, ignores 2nd line which is a comment (ignores \ at end of comment), and runs 3rd line "exec" cmd. That calls a pgm pt_shell which has a source $0 in its arg. pt_shell replaces the current shell and sources myscript.sh (which is the original file itself). However, pt_shell is a pgm that only sources tcl file, and starts parsing it per tcl syntax. It sees any line starting with # as comment . So, it ignores first line as comment (even though tclsh would have interpreted that, but looks pt_shell just ignores it). It looks at 2nd line, sees continuation of it on 3rd line (due to \ at end of 2nd line), and so considers 2nd line as comment (with 3rd line included in it). Then it moves to 4th line and start reading the file normally. So, here we are able to use the same file as both script and source file.

#!/bin/sh

# \

exec pt_shell "source $0"

other tcl cmds ....

variables:

Just like any other language, we define variables in tcl. Variables can be any data type. Tcl has it's own data type explained below. variable names need to conform to a syntax. In tcl, these var names or identifiers start with letter or _, followed by letters, digits, $,_. ex: a123_. However going thru tcl doc, it looks like a standard scalar variable can be comprised of any character. However the variable substitution operator "$" assumes you are using alphanumeric characters and underscores (plus name space separators "::"). So, we stick with _ and alphanumeric char for var names, so that they work w/o issues when used with $. To assign a value to a var, "set" cmd is used. To print value of any var, "puts" cmd is used. syntax of various cmds is explained in cmds section below.

ex: set a123! me => even though a123! has special char "!" in it's var name, tcl accepts it. It set var "a123!" to value "me"

puts $a123! => errors out, since $ assumes that var name is only _ and alphanumeric char. So, the first time it sees "!", it assumes that var name is complete and so var name is a123. ! is assumed to be start of next word. So, it tries to print value of var "a123", which obviously doesn't exist. Gives ERROR: can't read "a123": no such variable

If we put var inside curly braces, then no substitution is done, and $ works correctly.

puts ${a123!} => prints "me", since a123! is not substituted, but treated literally. So, $ sees a123! as var name. This technique also used when printing var concatenated to other char as "/a/b/${myvar}_c/d". Here, without curly braces, $ will look for var name "myvar_c" which doesn't exist.

unset a123! => This unsets the var so that it isn't defined anymore. However, if var a123! doesn't exist, then it will error out. So, the var needs to be set before it can be unset.

NOTE:  TCL is used almost exclusively with all CAD software to write scripts to be run on the CAD software. One thing to keep in mind is that when you run the same script multiple times or different scripts in the same session, var definitions from one iteration of the script or from multiple scripts are saved in memory, so the value of var from prior script or different script may still be used in current run. In such cases, the script may fail when run on a different session. Or you may get errors like "var myvar is not an array", when you want to set an array. This happens because var may be defined as normal var in previous iteration, and in latest run you are trying to make it an array, which conflicts with previous defn. In such cases you should always unset all var at beginnng of script, so that script starts clean. One issue is that var needs to be set before it can be unset. To get over that use catch.

ex: catch {unset a123!} => Now even if the var is not defined or set, it will not error out.

------------

In TCL there are many reserved words that can't be used as identifiers. ex: for, if, expr, etc. Also, tcl special var explained below can't be used as identifiers.

tcl special var: have predefined usage (i.e tcl_version, env, argc etc)

  • puts $tcl_version => gives 8.6
  • puts $env(PATH) => prints PATH = /home/cg/root:/..../:
  • argc = num of cmd line args, argv = list of cmd line args, argv0 = name of script being invoked

whitespace: whitespace (blanks, tab, etc) is needed in tcl to separate parts of stmt so that interpreter can identify them. Sometimes, it's not necessary, i.e [expr 3+2] will work correctly even though 3+2 has no whitespace. Recommended to use whitespace everywhere, so that there's no confusion.

Example script in tcl:

run standalone:

ex: hello.tcl
#!/usr/bin/tclsh => tells this script should be interpreted using tclsh.
# comment is here => comment. multiline comment would be using "if 0 { comments }". We do not have C style of multi line comment.
puts "Hello World!" => io cmd. puts is cmd, "Hello World" is arg. no semicolon at end. semicolon is needed if inline comment is used => puts "abc"; #comment.
puts stdout "Hello" => here 2 args provided. prints Hello on screen (std o/p)

run: ./hello.tcl or tclsh hello.tcl. If 1st line #! is not there in tcl script, then ./hello.tcl doesn't work (as current shell doesn't know what interpretor to use to run this file). Note: extension of file doesn't have any meaning in linux.

run interactively:

Instead of writing script, we can run tclsh in interactive mode by typing tclsh. Then shell changes from whatever shell you are currently in to tclsh. You can type tcl cmds in that shell, and it will show corresponding outputs. To exit tclsh, type exit and enter.

ex: [/home/ajay]$ tclsh => causes it to switch to tclsh
% expr 2 + 3 => n tclsh, we issue tcl cmds
5 => result of tcl cmd

% exit => cmd to exit tclsh

[/home/ajay]$ => after typing exit above, we are back to bash or csh


Data and Cmd:

Data may be integer, string,etc and have a identifier name (as my_name, etc). Cmds (or operators) are +,-,while, for etc. There are reserved words that are used for cmds which can't be used as identifier names. In many languages (eg C) data types need to be defined as to whether it's an integer, float, string, etc, while in most interpreting/shell languages (eg tcl) data types do not need to be defined, and are figured out automatically by the interpretor. This can be a source of confusion, as we do not know if a particular variable is treated as string or number in a particular operation. Many times, it's clear as some operators only operate on particular data tpes (i.e + only works on int and float), but sometimes it's more complex.

Data types: primitive data type is string (tcl is called string only lang). Everything is a string, and converted to other types as needed. No declaration needed. There are 2 composite (or derived) data types: list and array. So, this keeps the languauge very simple. Tcl can auto transform one data type to another when needed. However this makes the lnguage slow, as adding 1000's of numbers will require converting numbers from strings to number (as all numbers are by default treated as string, and then interpreted to be numbers which requires expensive conversion).

Following are the data types in tcl:


1. string: This is the only primitive data type. string can be put w/o quotes (for single words), with double quotes (for multiple words where we want weak quoting, i.e cmd, var and backslash substitutions allowed), or with curly braces (for multiple words where we want strong quoting, i.e no substitutions allowed).
ex: set myVariable 18 => stored as string, even though no quotes used, since it's single word.
puts [expr $myVariable + 6 + 9] => myVariable is converted to integer and 33 is returned, which is again stored as string and passed on to puts, for puts to print it.

ex: set me "John Hass" => double quotes used since it's more than 1 word assigned to var "me"

ex: set me {John Hass} => same as above.

string operations: manipulate strings. Lots of such cmds available. Listing a few below. they all need to have keyword "string" before using the operation cmd

compare: string compare string1 string1 => returns 0 if string1 equals string2.

ex: if {[string compare $s2 $s3] == 0} { puts "Strings same"; }

equal: string equal $s1 $s2 => returns 1 if both strings are same

map: string map {abc 1 ab 2 a 3 1 0} 1abcaababc => replaces substring in string "1abcaababc" with key value pair. So, abc is replaced by 1, ab is replaced by 2 and so on. Result is 01321.

index: various cmds to get index of string
ex: string first string_to_find string1 => finds first occurreence of string "first string_to_find" in string "string1". Similarly we have "last" cmd to find last occurrence of string.
ex: string index $s1 4 => finds character at index 4 of string $s1

ex: string length $string1 => finds length of $string1

subst: This command performs variable substitutions, command substitutions, and backslash substitutions on its string argument and returns the fully-substituted result. If any of the -nobackslashes, -nocommands, or -novariables are specified, then the corresponding substitutions are not performed. When it performs its substitutions, subst does not give any special treatment to double quotes or curly braces (except within command substitutions)

ex: set a 44; subst {xyz {$a}} => returns ``xyz {44}'', not ``xyz {$a}''

ex: set tmp1  [subst -nocommands {${var1}isprime/name/${var2}/A1}] => Here var1 and var2 are substituted by their values and var "tmp1" is set to the resulting string obtained

format: format cmd formats string in fashion similar to C sprint cmd using % conversion specifier.

  • %d => convert integer to signed decimal string,
  • %f => Convert number to signed decimal string of the form xx.yyy. If we have %5.2f, it means 2 decimal precision with minimum field width of number (including all the digits and the dot) to be 5. It will pad with spaces to make it 5 wide if the total width turns out to be less than 5. ex: format %6.2f 78.345 => prints " 78.34". Note the space added in the front to make it 6 field wide. If we want to pad with "0" instead of space, we should do %06.2f, then it outputs "078.34".

ex: format " Today, %d shares in %s were bought at $%.2f each" 123 "Global BigCorp" 19.372 => Prints: "Today, 123 shares in Global BigCorp were bought at $19.37 each". Here %0.2f means 2 decimal precision.

2. list: This is a composite data type. It's a group of elements, put within quotes " ... " or curly braces { ... }. list is an ordered collection of items. List can contain any kind of items, even other lists.

List created using any of below:

ex: set myVariable {red green blue} => assigns list to var. Could have used " " instead of {}

puts $myVariable => lists can be printed by providing list name, prints "red green blue"

ex: list a b => returns {a b} or "a b" => other way to create list is to use list cmd explicitly. So, set myvar [list a b] is same as set myvar "a b"

IMP: Be careful when using {} when making a list. If there are var, then it won't work, and we need to use "" if we want to subs those var. ex:

  • set tmp1 "aca"; set tmp2 "baa";
  • set mylist [list {a $tmp1} {b $tmp2}] => Here {a $tmp1} is assigned to index 0, {b $tmp2} to index 1. No subs done.
  • set mylist [list [list a $tmp1} {b $tmp2}] => Here {a aca} is assigned to index 0, {b baa} to index 1. Subs done. Correct way if subs is desired.

list operations: Many operations can be performed on lists, as sorting, getting nth element of list, etc. There are many list cmds available that can do that. list is most widely used dat type in Tcl, so knowing cmds that do operations on list is very helpful. Below are few imp ones:

lappend => appends to list

set var1 orange
lappend var1 " " "blue" => appends blue and empty var "" to var1. Empty var does nothing to var1. instead of lappend, we can use append.

llength => length of string (if 2 elements in string, this returns 2)

puts [llength $var] => gives length as 2

lindex {list} index => returns element from list corresponding to said index. list's first element is index 0.

puts [lindex $myVariable 2] => prints index[2] which is "blue"
puts $myVariable => prints all elements of list


lindex {a b c} 0 -> a
lindex {a b c} 2 -> c
lindex {a b c} end -> c
lindex {a b c} end-1 -> b
lindex {a b c} -> a b c (no index provided, so the whole list is returned)
lindex {a b c} {} -> a b c

lindex {a {b {c d} e} f {g h} i} => This list has lists within the list (nested list). Here index 0 is "a", index 1 is "b {c d} e", index 2 is "f", index 3 is "g h" and so on. to access sub-elements of index 1 , we need to assign index 1 to a var, and access each element using lindex for that list, i.e set tmp [lindex { .... } 1]; lindex $tmp 2 => returns index 2 of {b {c d} e} which is "e".
lindex {{a b c} {d e f} {g h i}} 2 1 -> h (2 dim list, so equiv to lindex (g h i} 1 => h

lassign => xform list to var:
set var {orange blue red green}
lassign $var colour1 colour2
puts $colour1 => color1 gets assigned orange, while color2 gets blue

lsort => sort a list. default is ASCII sorting, unless options -integer, -real, -dictionary is used. ASCII sorting is basically sorting based on ascii value of each character.
set var1 [lsort $var] => var1 is ASCII sorted list (in increasing order, i.e smallest items first). -increasing sorts in increasing order (default), while -decreasing sorts in decreasing order.

lsearch {list} search_term => lsearch returns the index of the first element in list that that matches pattern, or -1 if there are no matches. Before Tcl 8.5 introduced the in operator, lsearch was very frequently used to test for the existence of a value in a list.

ex: if {[lsearch $var att] > -1} {

...

}

3. Simple array: This is a another composite data type like list. Just like var, no need to define array. Here array indices are integers like 0,1,2, etc.

ex: array_name (array_index) => NOTE: round brackets used instead of square brackets (square brackets are used in C pgm). In Tcl, square brackets used for cmd substitution.

set color(0) purple

set color(1) red

puts "$color(0) $color(1)" => prints purple red. If we don't put "" then it errors out since puts can take only 1 arg, while here there are 2 args

puts [array size color] => "size" returns the size of array "color". Here it returns 2.

To read elements of array, we can iterate thru indices using for loop (in case they are continuous), loop ending when we reach the size of array.

4. Associative array => associative array is a more genric form (or a superset) of simple array, where array indices can be anything (doesn't have to be limited to numbers). All scripting languages support associative array as it's very helpful. It's in key(index) value pair. Index doesn't have to be a number like 0, 1, 2, etc as in simple array. When index is string, it's called associative array. index is number in C like language, but in tcl or other scripting languages that support associative arrays, it can be anything.
ex: set marks(english) 80
array set marks english 80 => same as above. NOTE: no ( .. ) when using array set.

array set val [list a 1 c 6 d 3] => multiple var set here, by having more than 1 pair. So, array val(a) set to 1, val(c) set to 6 and so on. list is optional

array set val {a 1 c 6 d 3} => equally valid form, same as above. NOTE: curly braces used instead of [ .. ] since list is a cmd that returns o/p.

array set val [list a [list clk [list c d] e] z [list x y]] => here array elements themselves are nested lists, so val(a) = "clk [c d] e" val(z) = "x y". NOTE: these are not treated as nested arrays or 2D arrays or anything, it's still 1D associative array, just that each element of array is nested list. We can access each sub-element of val(a) by using list cmd as "lindex" etc. i.e "lindex $val(a) 1" gives "c d"


puts $marks(english) => prints 80, puts $marks is an ERROR as all indexes of array can't be printed directly, each index has to called separately
puts [array size marks] => returns 1 as size of this array

set marks(student) "Dave" => here array marks stores "Dave" and not number. Each element of array can store anything (obvious as everything is a string)

puts [array get marks] => "array set" and "array get" are most popular for setting array and getting array. " array get" returns a list containing pairs of elements (index and value) of the array.

ex: we can get all array elements by using [array get ...] cmd above, but we can also traverse using foreach loop.

foreach {subject number} [array get marks] { puts "$subject -> $number" } => here we get index and value in each iteration of loop.

puts [array names marks] => "names" prints indices of array. here it prints "english" since that's the only index. This is used to iterate thru indices of associative array using for loop, compared to using "size" to iterate thru regular arrays. "array get" could also be used to get all elements of array directly or going thru a loop.

ex: foreach {idx_key} [array names marks] { puts "$marks($idx_key)" } => prints value of each element of array

puts [array names marks english] => here it prints the index english, if it exists in marks array. since $marks(english) exists, it prints "english". If english was not a valid index, it would have printed nothing.

ex: index can be hyphen or anything in combination of special char.

set results(-noexecute) false

set Me $results(-noexecute) => sets Me to "false"


operators:

arithmetic: + - * / %
relational: == != < > <= >=
logical: && || !
bitwise: & | ^ << >>
Ternary: ?: => If cond=true ? val=x : val=y
math func: abs, sin, cos, log, rand, etc
system func: clock, open, close, exec(execute system cmd)
ex: puts [exec ls] => executes ls cmd, and output is printed on screen

NOTE: there is no "=" operator in tcl as in C or other languages. To assign something as in tcl, we use "set" cmd explained next.


Cmds:

1. set => sets a vraiable. In most other languages "=" sign is used to assign a var, but in tcl, set is used instaed (there's no equiv "=" sign in tcl)
----
set a 5 => sets var a to 5. Use $a to access a. puts $a will print 5. here 5 is string type
set a "me" or set a {me} or set a me => sets var a to string me. NOTE: "set a me you" won't work as "me you" is 2 words and not interpreted as 1 string, unless put in quotes => i.e set a "me you"
set colorlist {red green} => list is group of words within {} or "". $colorlist lists all the elements
set colorlist1 [list red green] => same result as above, list is optional
set colorList3 [split "red_green_blue" _] => the cmd split, splits the colors and then list is set those 3 colors. => puts $colorList3 => red green blue

2.

3. expr: In tcl, we can't directly do any arithmetic, as everything (incl numbers) is treated as string, so string operation is done.Instead we use cmd "expr" or expression to indicate it's an arithmetic expression.

ex: set a 2; set b 5; set c $a+$b => sets c to 2+5 and NOT 7. To fix this we need sto do this: set c [expr $a+$b] => sets c to 7. 

4. exec:

5. regexp:

regular expressions: pattern match similar to perl can be done here. regexp is used everywhere in unix world. See the regular expression page. tcl regexp is consistent with linux regex standard. However, here regexp has to be inside { }. This prevents var expansion (i.e $a will be treated as literal $a without any expansion for var a). If you want expansion of var, then you need to use double quotes for pattern "pattern". Also regexp supported seems to be ERE and not BRE. So, () and {} are treated as metacharacters and NOT literals.

NOTE: Tclk 8.1 and above has moved to advanced regex, so the behaviour for tcl versions before and after 8.1 may be different. See this link for tcl regex: https://wiki.tcl-lang.org/page/Regular+Expressions

NOTE: When you move from {} to "" for patterns, make sure that additional escape chars are added. For e.g., if you had {\s*}, change it to "\\s*". I don't know the reason. Double quotes have unexplained behaviour, so I stick with {...} in patterns, as they follow what you would expect of regex behaviour.

  • ex:  regexp {.*\.\*$} $mytmp => This matches .* at the end of string. If we replace {..} with "..", then it matches anything as \.\* are interpreted as .*.*. However to match {...} behaviour, we have to put \\ before . and before *, i.e regexp ".*\\.\\*$" $mytmp => This matches the bahivour of {...} where .* is matched at the end of any string.

syntax: regexp optionalSwitches patterns searchString fullMatch subMatch1 ... subMatchn => patterns are searched for in searchString. Full match is any variable to hold the result of matched regex result. Submatch1 to SubMatchn are optional subMatch variable that holds the result of sub match patterns. These submatch happen if we have () inside the pattern, which implies that we want to hold the result of this match in a separate var.

return value of a regexp is 1 if matched, and 0 if it didn't match. So, this can be used in an if-else condition (NOTE: put [] around regexp to pass the return value)

  • regexp abc abcd => Simplest regex cmd, where "abc" is pattern searched in string "abcd". Here, it matches, so it returns 1.
    • if {[regexp abc ab]} { ... } => This doesn't match (as pattern abc isn't in string "ab"), so if part not executed.
  • regexp {([A-Z,a-z]*)} "Tcl Tutorial" a b => regexp is cmd that searches for string with any alphabets in the given string "TclTutorial". Any pattern matched string is assigned to var a. Since there is () inside the pattern, other match is also done in exactly same way, but the result is now stored in another var b. This returns a=Tcl, b=Tcl.
  • regexp {([A-Za-z]*.([A-Za-z]*))} "Tcl Tutorial" a b c => Here a is matched to the whole thing, with b also matched to whole string. c is matched to string within b as sub pattern (A-Za-z]*) is inside ([A-Za-z]*.([A-Za-z]*)). So, a=Tcl Tutorial, b=Tcl Tutorial, c=Tutorial
  • regexp {[0-9]+[ ]+:[ ]/} "12: 34 : 1234 : /My name" => this regexp matches 1 or more digits followed by 1 or more whitespace, then single : followed by 1 whitespace, ultimately followed by /. So here it matches "1234 : /". NOTE: whitespace match by giving a space inside square brackets. i.e: [space]. To match any of newline, tab or space do:  [\n\t ]+

switches: There are multiple switches or options that can be applied to regex via -. A "--" (double dash) implies end of switches.

  • -nocase: ignore case
  • -line: matching done only on a line, ignoring characters after a newline.
  • -start: Sets the offset of start of search pattern. So, -start 4 starts searching after 4th char (i.e 5th char onwards)
  • -- end of switches.

ex: regexp -nocase -line -- {([A-Z]*.([A-Z]*))} "Tcl \nTutorial" a b=> Here a and b, both match "Tcl" as newline prevents the regexp to go to next line (-line option)

regsub: Just like regexp is used for seraching matches, regsub can be used to substitute regex matches in a string.

Syntax: regsub opt_switches regexp subject replacement opt_resultvar => If opt_resultvar not specified then substituted string is apssed to the result of this cmd (which may be used to set another var, etc). -all switch matches and replaces all instances of patterns in the string. Without this switch only the first matching range is found and substituted.

  • ex: regsub -all {\mfoo\M} $mystr bar my_str => Replace (in the string in variable mystr) every instance of foo which is a word by itself with bar. Stores the final result in the same string my_str (NOTE: no $ in resultvar)
  • ex: regsub -all {.tcl} $my_list "" my_list => remove .tcl in $my_list var.

6. io cmds: cmds for input output of data. Just like scanf and printf in C. We use puts for printing, and gets for getting input. o/p cmd "echo" doesn't work in tcl, unless we do exec (i.e exec echo "abc"). However, echo does work when tcl files having "echo" cmds are sourced into CAD tools. I've many examples that use "echo" instead of "puts". That's not correct, and may not work. Use "puts" instead when working in tcl, as that's the proper print stmt.

  • A. puts: To output to screen or other files
    • puts -nonewline string => writes characters given by string on channelId which by default is stdout. puts normally outputs a newline character after string, but this feature may be suppressed by specifying the -nonewline switch.
      • ex: puts "my name is $name" => This prints "my name is Raj", assuming var "name" is set to Raj.
      • formatted printing: Similar to printf in C, we can formt o/p by using format cmd in tcl, which formats any string. See format cmd abobe in strings
        • ex: puts [format "%0.2f" $rate] => string is first formatted and then passed to puts cmd.
      • ex: Append a log message to a file:
        • set chan [open my.log a] => nested cmd open inside []. chan is set to filehandle returned by opening my.log in append mode (a=append, r=read, w=write)
        • set timestamp [clock format [clock seconds]]
        • puts $chan "$timestamp - Hello, World!" => since file is in append mode, this line is written at the end.
        • set file_data [read $chan] => reads file and stores contents in file_data. File has to be open in read mode.
        • puts $file_data => prints all contents of file_data on screen
        • gets $file_data data1 => gets data from file one line at time, and puts in var data1. use a loop to read whole file
        • close $chan
  • B. gets: To get input from screen or other files.
    • ex: Get a number from user and print it
      • puts "Enter a number
      • gets stdin my_var=> gets the input from std input (keyboard) and stores it into var named "my_var"
      • puts "Num entered is $my_var"
  • C. redirect => to redirect o/p of cmd to a file. We provide the name of the file (and NOT the pointer of file when doing "open file"). Reason is redirect will open and close the file for you, instead of you trying to open a file in wrt mode, write into the file and then close the file. This becomes very convenient as it's all done under the hood with redirect cmd. Do NOT use file pointer with redirect cmd. It will not write anything, nor will it show any error.
    • ex: redirect -file ~/tmp_file {[puts $marks(Mark)]} => -file specifies file_name to redirect the o/p to. Anything within { ... } is redirect to the file. "puts" is needed since it prints the o/p of cmd. Std unix redirection cmd ">" will not work for redirection.

8. flow ctl cmds: controls flow, via loops, conditional stmt, etc. Similar kind of keywords as in C: if-else, while, for, etc. Syntax for these is very stricyt in tcl, where starting/closing braes have to be in

A. if - else: You have to add if else start and end curly braces on same line (as shown below), except for the  final closing curly braces }. If we put starting or ending curly braces on separate line else it will error out with "unkown cmd {".
if {$x == 0} { => IMP: if starting curly braces should be on same line, exactly like this. You can't write if {$x == 0} on 1st line and "{" on next line, as then the cmd if won't be parsed correctly. gives error "wrong #of artgs" for if.
echo "Equal"
} elseif {$x > 0} { => IMP: write elseif exactly like this. ending "}" for if and starting "{" for elseif need to be on same line, so that elseif cmd can be recognized correctly
echo "Greater"
} else { => IMP: write else exactly like this. similar to elseif above
echo "Less"
} => This could also be put in previous line.

ex: if {$x == 0} {echo "Equal"} elseif {$x > 0} {echo "Greater"} else {echo "Less"} => This is same as above cmd, but everything on 1 line. This is equally valid, as all cmds can be parsed correctly here (since there are matching starting closing braces around keywords)

B :? => used to replace if else
set b [expr $a == 1 ? 20: 30] => assuming a=5, then, b is set to 30
#while
set p 0
while {$p <= 10} {
echo "$p squared is: [expr $p * $p]"; incr p
}

C. for: rewriting while loop shown above
for {set p 0} {$p <= 10} {incr p} {
echo "$p squared is: [expr $p * $p]"
}

D. foreach: implements a loop where the loop variable(s) take on values from one or more lists.
#simple list
foreach el [lsort [array names a]] { => each array name a is assigned to el
echo "a\($el\) = $a($el)"
}
#multiple items from list
foreach {name gender} [list Bob male Sarah female Kim hermaphrodite] {
... => name and gender assigned to "Bob and male" in 1st iter, then "Sarah and female" in 2nd iter, so on. $name and $gender can now be used inside the loop.
}
#multiple lists traversed simultaneously
foreach a [list 1 2 3 4] b [list 5 6 7 8] c [list a b c d] {
puts "$a $b $c" => "1,5,a" is printed in 1st pass, then "2,6,b" and so on.
}

E. break, continue: terminate a loop before the termination condition is reached, as follows:
The break command causes the innermost loop to terminate.
The continue command causes the current iteration of the innermost loop to terminate.

F. switch: equiv to if tree
switch $x {
a {incr t1}
b {incr t2}
c {incr t3}
}

9. tcl special cmds: These are special cmds, which are used very often inscripts, so I've listed few of them.
A. info script:
echo "Sourcing [ file normalize [ info script ] ]" => info script returns name of file containing the script that is currently being evaluated. file normalize file1 => returns normalized filename (no ../ etc) for name1. This echo line should be first line in any script to print name of file/script being run. For some reason, this gives an error with "echo is unknown cmd". Use puts [exec echo "..."] instead. Prints => Sourcing /home/kagrawal/scripts/hello.tcl

B. info exists:
[info exists ::env(VAR1)] => returns 1 if such env var VAR1 exists, and is defined, else returns 0.
set a 3;
info exists a => returns 1
info exists $a => returns 0, since $a is 3, so it looks for var named 3, which doesn't exist.

10. procedures: Similar to functions in C.

#procedures: reusable procedures can be written. proc name {args} {body}

var d; set glb_d "my name";
proc plus {a b} { variable c; upvar $a arg1; return [expr $a + $b] } => 1 line proc defn to add two numbers. expr=expression. Here variable "c" is only seen within plus namespace (see namespace section below). To see it globally outside the proc, there are 2 ways:

  1. global: Decalraing a var as global inside a proc, allows it to be seen outside the proc.Same "global" keyword is also used to make a var declared outside the proc to be visible inside the proc.
    • In above ex, use "global c" inside the "plus" proc. Then var c is accessible outside the "plus" proc
    • In above ex, use "global glb_d" inside the proc. Then var "glb_d" is accessible only inside the proc "plus". If we have "global glb_d" outside the proc, then it will have no effect, and won't be accessible inside the proc.
  2. upvar: upvar creates link to variable in a different stack frame. It simulates "pass by reference" seen in other languages as C. This is able to handle complex situations which global can't handle.
    • Syntax is upvar <level> <other_var> <my_var>. <my_var> can access <other_var> from corresponding stack frame. Default <level> is 1. We can provide <level> as number 0, 1,2, etc or #0, #1, #2, etc. When # used, the meaning changes slightly. "upvar #0" means at global level (i.e get values from top level). In above ex of proc, upvar makes arg1 an alias for var a, so that arg1 can be used inside proc w/o issues. Generally convenient to do it this way. Only use #0 or #1 (as per tcl doc) or you are looking for trouble.
    • In above ex: use "upvar #0 glb_d loc_d" inside the proc. Then "loc_d" var link is created for "glb_d". Now if we modify glb_d or loc_d, both var get modified as they are refrring to same mem location. Ex:if inside the proc, we do => set loc_d "you". then both glb_d and loc_d are set to "you"


puts [plus 5 6] => proc being called with args. returns 11. NOTE: no brackets should be used for args when calling proc, or else it will give "Error: wrong # args". Also proc called has to be put into [...] when it's being used as arg to other cmd. Else "plus" keyword will be treated as data, and will error out. if proc is used by itself on a line then we can write "plus 2 3" (without the quotes) and it will work fine. However, if we use [plus 2 3] by itself on a line, then we get [5] (with the sq brackets) which is not what we want.

 

define_proc_attributes => Defines attributes of a Tcl procedure,
ex: define_proc_attributes sta_flow -info "configure pt" -define_args { => -info "text" gets printed when help is typed for this proc, help -verbose prints all possible args below which is a list of lists. Can be just 1 arg too.
{-design "valid design name; NOTE: usually defined by project.globals" "" string optional} => this is arg1
{-stage "valid design stage; NOTE: usually defined by project.globals" "" string optional} => This is arg2 and so on ..
}

ex: factorial
proc factorial {number} {
if {$number <=1} {return 1}
else return [expr $number * [factorial [expr $number - 1]]]
}
puts "factorial of 5 is" factorial 5 => prints 120

ex: info body factorial => This returns the code in the proc "factorial". This is helpful during debug, so that we don't have to find where that proc is.

11. namespace: Namespace is a container for set of identifiers that is used to group variables and procedures. Namespaces are available from Tcl version 8.0. Before the introduction of the namespaces, there was single global scope. Now with namespaces, we have additional partitions of global scope.
Namespaces are created as below:

namespace eval MyMath { => creating namespace MyMath
variable myResult => var inside namespace, can be accessed via MyMath::myResult
proc Add {a b} { .... } => This proc Add is only within MyMath namespace
namespace eval name2 { ... variable myResult ...} => namespace can be nested too. ref would be ::Mymath::name2::myResult
}
proc MyMath::Add {a b } { => create proc "Add" inside MyMath namespace, need to explicitly use ::, since this proc is defined outside MyMath amespace { ... }
set ::MyMath::myResult [expr $a + $b] => assign MyMath var MyResult to result, :: refers to top level global namespace, and then further :: refer to lower level. We can omit global ::
}
MyMath::Add 10 23 => this sets MyMath::myResult to 33
puts $::MyMath::myResult => prints 33, we can omit global ::, i.e puts $MyMath::myResult

To avoid using lengthy scope resolution operator, we can import/export namespace
namespace eval MyMath {
variable myResult
namespace export Add => This says that make "Add" available to whoever imports MyMath. Using export/import allows names to be shortened
namespace export Add1 => export another proc Add1, do similarly for other proc that you want exported
}
proc MyMath::Add {a b } {
return [expr $a + $b]
}
namespace import MyMath::* => Here everything that has been exported, is imported in global namespace
puts [Add 10 30] => Since "Add" has been imported, it is available in global namespace, Var still available as $MyMath::myResult

12: packages: similar concept as in other pgm languages, where a group of unctions, header files, etc can be put in a set of files, which can be acccessed as a single entity.

creating packages => consists of collection of files (tcl scripts, binary, etc) that provide specific functionality. It has a pkg name. Once created, pkg can be called in other tcl pgms, by specifying path of pkg
To create a package with TCL, you need two files :
  1.  the package file which contain your code
  2. the index package file to declare your package

These are the stpes to create pkg and then use it in a pgm:

I. package file: create a tcl file called HelloPkg.tcl. This defines the pkg called "HelloP" with all procs and var in it. NOTE: name of pkg file (HelloPkg.tcl) can be different than name of pkg (HelloP) as well as the namespace "HelloWorld". Strictly speaking, namespace is not required, as we just load this file, so procs and var will be available to the whole pgm loading this file. However, convention is to have a namespace so that if multiple pkg are being loaded, and they happen to have same name of proc or var, then they will keep overwriting each other, creating errors in results. Also, we usually keep namespace and pkg name the same, so that it's easier to know from which pkg, the particular proc is coming from.

namespace eval ::HelloWorld {
        namespace export Talk => export proc Talk, so that it's available to anyone who uses "import HelloWorld::Talk"
        set version 1.0
        set HelloWorldDescription "HelloWorld"
        variable home [file join [pwd] [file dirname [info script]]] => Variable for the path of the script
}

proc ::HelloWorld::Talk {} { => define proc Talk
    puts $HelloWorld::HelloWorldDescription
}
proc ::HelloWorld::GetPath {} { => define proc GetPath
    variable home
        return $home
}
package provide HelloP $HelloWorld::version => "package provide" actually creates pkg. NOTE: we defined name of pkg as "HelloP" while namespace is "HelloWorld"

package require Tcl      8.0 => This is needed incase Tcl version is < 8 (then namespace won't work, as they are defined only for version 8 and higher)

II. index package file: This index file is simply a Tcl file which is loaded into the interpreter when Tcl searches for packages. This file is named pkgIndex.tcl.  pkgIndex.tcl can be written manually or can be autogenerated. We need to add this 1 line in pkgIndex.tcl:

package ifneeded HelloP 1.0 [list source [file join $dir HelloPkg.tcl]] => "package if needed <pkg_name> <version> <pkg_filename>" cmd loads this pkg (pkg_filename) in response to cmd "package require <pkg_name> <version>". pkgIndex.tcl file is sourced on application startup, but pkg files are not loaded at that time since "ifneeded" is used. If "if needed" is not used, then <pkg_filename> will be loaded while sourcing of pkgIndex.tcl, which may be unnecessary, as not all packages are used in every pgm. "ifneeded" prevents pkg from getting loaded. The second part of this cmd says what files to source when this pkg is going to be loaded. We need full path of pkg file for sourcing to happen. $dir is the path of dir where HelloPkg.tcl resides, so "file join" provides full path of file. NOTE: $dir is not a std var, and "echo $dir" returns "unknown var" error, but it works for some reason when used in pkgIndex.tcl.

package ifneeded HelloP 1.0 [list source [file join $dir hello1.tcl]]\n[list source [file join $dir hello2.tcl]]\n[list source [file join $dir hello3.tcl]] => We can load multiple files too for 1 pkg

To auto generate this file, we can cd to dir where our pkg file "HelloPkg.tcl" is located, open up tclsh, and run "pkg_mkIndex . *.tcl" cmd. This will generate " pkgIndex.tcl" file in that dir for that pkg. If there are multiple packages in that dir, then pkgIndex.tcl will have multiple lines, 1 for each package.

III. use package in external pgm:  Tcl searches for index pkg file in "auto_path" or "tcl_pkgPath" which store default paths to search.

echo $auto_path => /usr/local/lib /usr/share/tcl8.5 /usr/lib64/tcl8.5 /usr/lib64/tk8.5 /usr/share/tk8.5

echo $tcl_pkgPath => /usr/lib64/tcl8.5 /usr/share/tcl8.5 /usr/lib64/tk8.5 /usr/share/tk8.5

Since we may not want to put our local file in any of these standard /usr/ dir, we append any of these paths to add the path where our pkgIndex.tcl is going to be put. Now to use this pkg in any tcl pgm, add these 3 lines in the pgm, Hello.tcl

lappend auto_path "/home//scripts/test" => This is the name of dir where pkgIndex.tcl is located, we append this path to the search path
package require HelloP 1.0 => "package require" cmd loads pkg. here pkg "HelloP" version 1.0 is loaded.
puts [HelloWorld::Talk] => Once pkg loaded, all proc and var accessible via namesapce.

puts [HelloWorld::GetPath]

puts $HelloWorld:::HelloWorldDescription => To access a var in pkg, we need to precede it with $

On running the script, Hello.tcl, we get this o/p:

HelloWorld
/home/scripts/test
HelloWorld

12. Error cmds: Error handling in Tcl is provided with the help of error and catch commands. Basically it allows you to take a chosen action incase any cmd in script gives an error. This is useful for debug purpose in production.

syntax:

error message info code => message=error msg, info=error info stored in global var "errorInfo". code=error code stored in global var "errorCode"

catch script_or_cmd ErrorVar => script_or_cmd is any cmd that you normally execute in your tcl script, ErrorVar is the name of var used to store the "Error msg" that is provided in the error cmd above. Catch cmd returns 1 incase of error, else 0. Useful to use it within if stmt, so that we print some debug info incase of error.

Ex:

proc Div {a b} {
   if {$b == 0} {
      error "Error generated by error" "Info String for error" 401 => Error var stored in global
   } else {
      return [expr $a/$b]
   }
}
puts "Result = [Div 10 0]" => Here we are executing Div proc normally. Since we provided 0 as one of it's operand, it will print error msg from above
if {[catch {puts "Result = [Div 10 0]"} errmsg]} { => Here, we catch that errorinfo from above, and take a defined path on printing these errorvar that were stored above in proc. This is more user friendly and also easier to debug.
   puts "ErrorMsg: $errmsg"
   puts "ErrorCode: $errorCode"
   puts "ErrorInfo:\n$errorInfo\n"

}

catch "exec myscript $a 4b" => this will catch the error, bt is not doing anything with it. This also doesn't have a var to store error msg. This is not very helful use of catch. We can use catch cmd with all other cmds, but then script will be very long and unreadable. So, we should use catch only with cmds that seem likely to fail, like "opening a file", etc.

13.

 



Tk = ToolKit

Tk provides cross platform gui widgets. It can be used with other dynamic lang, not just Tcl

Hello.tcl
---
#!/usr/bin/wish => wish interpreter is needed to run Tk cmds, if we use /usr/bin/tclsh, thne it will not run as any Tk cmds are not identified by tclsh
grid [ttk::button .mybutton -text "Hello World"]
---

./hello.tcl => draws a box with "Hello World" in it. We can also type > wish hello.tcl => not needed since wish interpretor is already there on 1st line

special var:tk_version, tk_library

#!/usr/bin/wish
puts $tk_library => prints path of where all std Tk libraries are. Note: all tcl cmds are still valid in Tk

widgets: basic component pf Tk based app. aslo called window
-------
main widget is root widget, and other widgets can be placed in it. basic gui widgets are buttons, menus, etc.

create widget:
type variableName arguments options => type= widget type as button,label, etc. args are optional. options range from size to formatting of each component

ex: label .myLabel -background red -text "Hello World" => root window is named with . and an element in window is named .button1. button1 is var name. . refers that it's in root window

basic widgets:
-----------
label = Widget for displaying single line of text.
button = Widget that is clickable and triggers an action
entry => accepts single line of text as i/p
message = Widget for displaying multiple lines of text
text = Widget for displaying and optionally edit multiple lines of text.
toplevel = Window with all borders and decorations provided by the Window manager.

layout widgets => frame, place, pack, grid (widget to nest widgets packing in diff dirn)

selection widgets

media widgets

ex:
grid [label .myLabel -text "Label Widget" -textvariable labelText]
grid [text .myText -width 20 -height 5] .myText insert 1.0 "Text\nWidget\n"
grid [entry .myEntry -text "Entry Widget"]
grid [radiobutton .gender.maleBtn -text "Male" -variable gender -value "Male"
-command "set myLabel1 Male"] -row 1 -column 2
canvas .myCanvas -background red -width 200 -height 200
pack .myCanvas
.myCanvas create arc 10 10 50 50 -fill yellow

window maager => to handle top level window. . refers to main window
wm option window arguments

ex:
toplevel .t => creates top level window with name "t"
wm maxsize . 800 800 => max size of toplevel window is 800 by 800. Can't be extended by more than that by stretching with mouse
wm minsize . 300 300 => min size is 300 by 300
wm title . "Hello" => title displayed on window is "Hello"
wm attributes . -alpha ".90" =>
wm geometry . 300x200+100+150 => width=300, height=200, x position on screen=100, y position=150. This position determines where on your monitor screen the window is going to show up.
grid [button .mybutton -text "Hello \n World"] => places button on 1st line of grid