Not everyone knows Tcl - this is not intended to be a replacement for learning Tcl, the intent of this chapter is to give you some idea of how the Tcl scripts work.
This chapter is written with two audiences in mind. (1) OpenOCD users who need to understand a bit more of how Jim-Tcl works so they can do something useful, and (2) those that want to add a new command to OpenOCD.
There is a famous joke, it goes like this:
The Tcl equal is this:
As in the famous joke, the consequences of Rule #1 are profound. Once you understand Rule #1, you will understand Tcl.
There is a second pair of rules.
Every Tcl command results in a string. The word “result” is used deliberately. No result is just an empty string. Remember: Rule #1 - Everything is a string
In life of a Tcl script, there are two important periods of time, the difference is subtle.
The two key items here are how “quoted things” work in Tcl. Tcl has three primary quoting constructs, the [square-brackets] the {curly-braces} and “double-quotes”
By now you should know $VARIABLES always start with a $DOLLAR sign. BTW: To set a variable, you actually use the command “set”, as in “set VARNAME VALUE” much like the ancient BASIC language “let x = 1” statement, but without the equal sign.
# bash example X=`date` echo "The Date is: $X" # Tcl example set X [date] puts "The Date is: $X"
set x "Dinner" puts "It is now \"[date]\", $x is in 1 hour"
The consequences of Rule 1 are profound.
Of course, whitespace, blank lines and #comment lines are handled in the normal way.
As a script is parsed, each (multi) line in the script file is tokenised and according to the quoting rules. After tokenisation, that line is immediately executed.
Multi line statements end with one or more “still-open” {curly-braces} which - eventually - closes a few lines later.
Remember earlier: There are no “control flow” statements in Tcl. Instead there are COMMANDS that simply act like control flow operators.
Commands are executed like this:
It sort of works like this:
for(;;){ ReadAndParse( &argc, &argv ); cmdPtr = LookupCommand( argv[0] ); (*cmdPtr->Execute)( argc, argv ); }
When the command “proc” is parsed (which creates a procedure function) it gets 3 parameters on the command line. 1 the name of the proc (function), 2 the list of parameters, and 3 the body of the function. Note the choice of words: LIST and BODY. The PROC command stores these items in a table somewhere so it can be found by “LookupCommand()”
The most interesting command to look at is the FOR command. In Tcl, the FOR command is normally implemented in C. Remember, FOR is a command just like any other command.
When the ascii text containing the FOR command is parsed, the parser produces 5 parameter strings, (If in doubt: Refer to Rule #1) they are:
Sort of reminds you of “main( int argc, char **argv )” does it not? Remember Rule #1 - Everything is a string. The key point is this: Often many of those parameters are in {curly-braces} - thus the variables inside are not expanded or replaced until later.
Remember that every Tcl command looks like the classic “main( argc, argv )” function in C. In JimTCL - they actually look like this:
int MyCommand( Jim_Interp *interp, int *argc, Jim_Obj * const *argvs );
Real Tcl is nearly identical. Although the newer versions have introduced a byte-code parser and interpreter, but at the core, it still operates in the same basic way.
To understand Tcl it is perhaps most helpful to see the FOR command. Remember, it is a COMMAND not a control flow structure.
In Tcl there are two underlying C helper functions.
Remember Rule #1 - You are a string.
The first helper parses and executes commands found in an ascii string. Commands can be separated by semicolons, or newlines. While parsing, variables are expanded via the quoting rules.
The second helper evaluates an ascii string as a numerical expression and returns a value.
Here is an example of how the FOR command could be implemented. The pseudo code below does not show error handling.
void Execute_AsciiString( void *interp, const char *string ); int Evaluate_AsciiExpression( void *interp, const char *string ); int MyForCommand( void *interp, int argc, char **argv ) { if( argc != 5 ){ SetResult( interp, "WRONG number of parameters"); return ERROR; } // argv[0] = the ascii string just like C // Execute the start statement. Execute_AsciiString( interp, argv[1] ); // Top of loop test for(;;){ i = Evaluate_AsciiExpression(interp, argv[2]); if( i == 0 ) break; // Execute the body Execute_AsciiString( interp, argv[3] ); // Execute the LOOP part Execute_AsciiString( interp, argv[4] ); } // Return no error SetResult( interp, "" ); return SUCCESS; }
Every other command IF, WHILE, FORMAT, PUTS, EXPR, everything works in the same basic way.
Where: In many configuration files
Example: source [find FILENAME]
Remember the parsing rules
find
command is in square brackets,
and is executed with the parameter FILENAME. It should find and return
the full path to a file with that name; it uses an internal search path.
The RESULT is a string, which is substituted into the command line in
place of the bracketed find
command.
(Don’t try to use a FILENAME which includes the "#" character.
That character begins Tcl comments.)
source
command is executed with the resulting filename;
it reads a file and executes as a script.
Where: Generally occurs in numerous places.
Tcl has no command like printf(), instead it has format, which is really more like
sprintf().
Example
set x 6 set y 7 puts [format "The answer: %d" [expr {$x * $y}]]
Where: Various TARGET scripts.
#1 Good proc someproc {} { ... multiple lines of stuff ... } $_TARGETNAME configure -event FOO someproc #2 Good - no variables $_TARGETNAME configure -event foo "this ; that;" #3 Good Curly Braces $_TARGETNAME configure -event FOO { puts "Time: [date]" } #4 DANGER DANGER DANGER $_TARGETNAME configure -event foo "puts \"Time: [date]\""
In the end, when the target event FOO occurs the TCLBODY is evaluated. Method #1 and #2 are functionally identical. For Method #3 and #4 it is more interesting. What is the TCLBODY?
Remember the parsing rules. In case #3, {curly-braces} mean the $VARS and [square-brackets] are expanded later, when the EVENT occurs, and the text is evaluated. In case #4, they are replaced before the “Target Object Command” is executed. This occurs at the same time $_TARGETNAME is replaced. In case #4 the date will never change. {BTW: [date] is a bad example; at this writing, Jim/OpenOCD does not have a date command}
Where: You might discover this when writing your own procs
In
simple terms: Inside a PROC, if you need to access a global variable
you must say so. See also “upvar”. Example:
proc myproc { } { set y 0 #Local variable Y global x #Global variable X puts [format "X=%d, Y=%d" $x $y] }
Dynamic variable creation
# Dynamically create a bunch of variables. for { set x 0 } { $x < 32 } { set x [expr {$x + 1}]} { # Create var name set vn [format "BIT%d" $x] # Make it a global global $vn # Set it. set $vn [expr {1 << $x}] }
Dynamic proc/command creation
# One "X" function - 5 uart functions. foreach who {A B C D E} proc [format "show_uart%c" $who] { } "show_UARTx $who" }