My current interest has to do with two programs that use Tcl as a "back-end" language. These are the Vivado FPGA design program from Xilinux and the OpenOCD program I use for JTAG devices and such.
Tcl was never intended to be used as a general purpose programming language. That doesn't stop it from being used as such of course. It was designed to be a small footprint scripting language that could be made a part of other programs. A big factor that launched Tcl into wild popularity in times past was the availability of the "Tk" toolkit for Tcl. This made it possible to use Tcl/Tk to quickly and easily write GUI programs. These days there are many much superior options for such things.
I'll point out that the use of Tcl by Vivado and OpenOCD is exactly the sort of thing Tcl was originally intended to be used for.
And for the record, the Tcl language was invented by John Ousterhout at UC Berkeley in 1988. He aimed to create an embeddable small footprint language so that developers would not need to invent their own languages. I remember attending a talk by him he called "bricks and glue" where he described Tcl as the glue to put together bricks provided by whatever application was being developed.
The first "surprise" is that typing "tcl" gives me "Command not found". The thing to do is to type "tclsh" and you will be greeted with a prompt. We can write the customary "hello world" program as follows:
#!/bin/tclsh puts "hello world"This gets us off to a start. I won't spend any time talking about Tk. What I will do right away is show you some things to illustrate some of the weaknesses of Tcl. This is not simply to throw mud at Tcl, but I think you are well served to know right up front some things that may cause you pain if you are used to other programming languages.
Try a statement like this:
x = 42Tcl will tell you "invalid command name "x"" -- what you need to do is:
set x 42This could be our first Tcl "lesson". Tcl is always looking for a command. The word "set" is a command, but just tossing the variable name "x" at it would require Tcl to recognize (and perhaps initialize) a variable in this context, which is not how Tcl plays the game.
Now try this program:
set x 42 puts xTcl will print "x", not 42 -- what you need is:
set x 42 puts $xNow try this:
set two 2 set sum $two+$two puts $sumThis prints "2+2". Tcl is full of surprises and unexpected behavior. The way to get the result we want here is as follows:
set two 2 set sum [expr ($two+$two)] puts $sumHow about that? Why the square brackets? We will get to that in a minute. My aim in all this is to show you that Tcl doesn't behave like any other programming language. The parser is very simple. It does not understand "infix" expressions (such as x=42 or sum=2+2). It views "x" as just another string unless we prefix it with a dollar sign to tell Tcl that what follows is the name of a variable.
Tcl has no types. It stores everthing as a string. Nothing is compiled in the sense we usually think about it. The Tcl interpreter just works on our code as strings, interpreting it as it goes (we will get to some implications of that shortly).
So, why the square brackets? The Tcl parser is simple and needs our help all of the time. We ran into this already with the need to prefix a variable name with a dollar sign. A good general statement is that everything is a string in Tcl until we tell it otherwise. Another general statement is that Tcl will interpret these strings as commands unless we tell it otherwise. The square brackets tell Tcl to run whatever command is inside them and replace the bracketed entity with the result of the command.
What about "expr"? This essentially introduces a sublanguage that knows about infix expressions that we take for granted in other languages.
And before we move on entirely, consider {} (curly brackets). These are really a way of quoting a bunch of text. Whatever is enclosed in curly brackets is collected up as a string and the text is passed along to whatever command might need it. You most often see this in conditionals (if statements) and procedure bodies.
#!/bin/tclsh
puts "All is well"
puts "sleeping"
after 2000 {
msg = "done"
puts msg
exit
}
vwait forever
The savy reader will notice that "puts msg" will not work, what we need is "puts $msg".
But I put this error in intentionally to illustrate the following.
If we run this program, we see:
All is well sleeping invalid command name "msg"The important thing is that we only see the error after the two second delay. The program starts and runs just fine with this error lurking. Most languages would detect an error like this (or a multitude of others) at compile time. With Tcl there is no compile time. The error is not detected until Tcl actually needs to run that line of code and attempts to interpret it.
This can be a serious problem in bigger programs. An error like this may not be detected until the program has been running for hours, days, even weeks perhaps. Imagine your airline ticket reservation system taking an infrequent branch months after deployment and tripping over a minor coding error. It happens all the time with Tcl programs.
Remember first of all that Tcl is typeless. Remember also that "everthing is a string". We thus have no boolean type. Strings holding integer values are false if they hold a "0" and are true otherwise. Strings in general are more interesting. The words yes, no, true, false, on, and off are recognized as you might expect. What happens if we test a string that contains "fish"? You get an error, you aren't supposed to do that.
if { expr } {
code
} elsif { expr2 } {
code
} else {
code
}
Note that there are no parenthesis in the above. It is all curly braces.
The expressions are surrounded by curly braces.
Your deeply ingrained habits from other languages go out the window here.
This is the basic game. You are encouraged to always use the curly braces around the code bodies. You are not required to, but unexpected substitutions can happen if you don't. The context of an if statement allows logical expressions with < == and such.
I try this:
set dog "fish"
if { $dog < 99 } {
puts "true"
}
Tcl accepts this without error. The condition never is true, regardless of the number value.
I have no idea what is going on here. There is an explicit way to get the length of
a string:
set len [string length "Hello World"]Here "string" is a multipurpose utility for use with strings and "length" is just one of many things it can do.
If you understand "if" you understand "while" so nothing more will be said.
The "foreach" loop works with a list:
set mylist { good bad ugly }
foreach x $mylist {
puts $x
}
The above makes an interesting lesson about when to use commas, dollar signs, and curly braces.
foreach x { red green yellow } {
puts $x
}
A "for" loop is ugly and looks like this:
for {set i 0} {$i < 5} {incr i} {
puts "Count: $i"
}
Once again, curly braces everywhere.
Note that we have introduced the "incr" function in Tcl, which we can use to avoid dragging in "expr"
in cases like this. We have "break" and "continue" to exit a loop or jump to the next iteration.
Curly braces in Tcl are really a flavor of quotes. They are quotes that postpone interpolation. Interpolation is where Tcl sees a dollar sign and injects the value of a variable. Tcl has plain old double quotes that do allow interpolation.
Consider this "for" statement.
for {set i 0} {$i < 5} {incr i} {
puts "Count: $i"
}
What is really going on here is that we have a command called "for" with 4 arguments.
In this example, each of the four arguments is a string enclosed in curly braces.
If you play some games, you can get Tcl to complain and coach you as follows:
wrong # args: should be "for start test next command"Consider the following code, which prints the word "dog" 5 times and gets rid of the curly braces on the "command" argument.
for {set i 0} {$i < 5} {incr i} "puts dog"
Now try this. Tcl complains "can't read "i": no such variable".
It is trying to perform the interpolation for $i early when putting
the for loop together and there ain't no "i" variable yet.
Using curly braces would postpone that interpolation and make that work.
for {set i 0} {$i < 5} {incr i} "puts $i"
To make that even more clear, consider this:
set i 99
for {set i 0} {$i < 5} "incr i" "puts $i"
This runs, printing 99 five times.
Notice also that I changed the flavor of the quotes around "incr i".
It doesn't matter here what flavor is uses as there is no interpolation
going on.
set_property IOSTANDARD LVCMOS33 [get_ports {GPIO_0_0_tri_io[*]}]
set_property SLEW SLOW [get_ports {GPIO_0_0_tri_io[*]}]
set_property DRIVE 8 [get_ports {GPIO_0_0_tri_io[*]}]
set_property PACKAGE_PIN T11 [get_ports { GPIO_0_0_tri_io[0] }];
This is pretty easy to recognize as Tcl.
Apparently Vivado has added "set_property" and "get_ports" to Tcl as
commands to the Tcl vocabulary.
The only suprising construction here is the "*" wildcard symbol.
At this point, I think this is something special that Vivado supports,
but I am not entirely sure.
#!/bin/lua msg = "Hello world" for i = 1, 5 do print ( 100 + i ) print ( msg ) endIt should be immediately obvious that this is a more civilized language than Tcl. In its day, Tcl may have been a good choice for an embedded language, but if Lua has a similar footprint (memory requirements), I would clearly chose it for new project. See the next section.
Standard Tcl however has grown and is no longer a good choice. There is JimTcl, which was once a stripped down Tcl, but it also seems to be growing. The claim is that it fits in 100K to 200K. There are other choices such as MicroTcl (160K) and "picol" which is said to be 550 lines of C code!
What about Lua? There are many variants and it offers a more civilized language. There is eLua, which can run in 20K of flash in certain configurations. On the Arduino it has been made to run in 80K of flash.
What about Python? There is MicroPython for embedded systems that runs in 256K of code and 16K of ram. There is also Circuit Python, which seems to be a trimmed version of Micropython without interrupts or thread support. Circuit Python is available as a binary image for some boards that I have, which would make it easy to try out.
I have several Pi Pico boards, as well as some Pi Pico 2 boards sitting next to me on my desk right now. Trying it would be as simple as downloading the UF2 file and flashing it.
Not to be outdone, Lua (in particular MicroLua) is available for the Pi Pico:
Lua, unlike Python, does not use indentation for code blocks, rather using do/end.
Some Lua cons:
It was written in 1993 (5 years after Tcl) by a trio of dudes in Brazil.
Tom's Computer Info / tom@mmto.org