Next: , Previous: FAQ, Up: Top


25 Tcl Crash Course

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.

25.1 Tcl Rule #1

There is a famous joke, it goes like this:

  1. Rule #1: The wife is always correct
  2. Rule #2: If you think otherwise, See Rule #1

The Tcl equal is this:

  1. Rule #1: Everything is a string
  2. Rule #2: If you think otherwise, See Rule #1

As in the famous joke, the consequences of Rule #1 are profound. Once you understand Rule #1, you will understand Tcl.

25.2 Tcl Rule #1b

There is a second pair of rules.

  1. Rule #1: Control flow does not exist. Only commands
    For example: the classic FOR loop or IF statement is not a control flow item, they are commands, there is no such thing as control flow in Tcl.
  2. Rule #2: If you think otherwise, See Rule #1
    Actually what happens is this: There are commands that by convention, act like control flow key words in other languages. One of those commands is the word “for”, another command is “if”.

25.3 Per Rule #1 - All Results are strings

Every Tcl command results in a string. The word “result” is used deliberatly. No result is just an empty string. Remember: Rule #1 - Everything is a string

25.4 Tcl Quoting Operators

In life of a Tcl script, there are two important periods of time, the difference is subtle.

  1. Parse Time
  2. Evaluation Time

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 langauge “let x = 1” statement, but without the equal sign.

25.5 Consequences of Rule 1/2/3/4

The consequences of Rule 1 are profound.

25.5.1 Tokenisation & Execution.

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 immedatly executed.

Multi line statements end with one or more “still-open” {curly-braces} which - eventually - closes a few lines later.

25.5.2 Command Execution

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:

  1. Parse the next line into (argc) and (argv[]).
  2. Look up (argv[0]) in a table and call its function.
  3. Repeat until End Of File.

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. Not the choice of words: LIST and BODY. The PROC command stores these items in a table somewhere so it can be found by “LookupCommand()”

25.5.3 The FOR command

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:

  1. The ascii text 'for'
  2. The start text
  3. The test expression
  4. The next text
  5. The body text

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 intepreter, but at the core, it still operates in the same basic way.

25.5.4 FOR command implementation

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 seperated 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.

25.6 OpenOCD Tcl Usage

25.6.1 source and find commands

Where: In many configuration files
Example: source [find FILENAME]
Remember the parsing rules

  1. The 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.)
  2. The source command is executed with the resulting filename; it reads a file and executes as a script.

25.6.2 format command

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]]
  1. The SET command creates 2 variables, X and Y.
  2. The double [nested] EXPR command performs math
    The EXPR command produces numerical result as a string.
    Refer to Rule #1
  3. The format command is executed, producing a single string
    Refer to Rule #1.
  4. The PUTS command outputs the text.

25.6.3 Body or Inlined Text

Where: Various TARGET scripts.

     #1 Good
        proc someproc {} {
            ... multiple lines of stuff ...
        }
        $_TARGETNAME configure -event FOO someproc
     #2 Good - no variables
        $_TARGETNAME confgure -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]\""
  1. The $_TARGETNAME is an OpenOCD variable convention.
    $_TARGETNAME represents the last target created, the value changes each time a new target is created. Remember the parsing rules. When the ascii text is parsed, the $_TARGETNAME becomes a simple string, the name of the target which happens to be a TARGET (object) command.
  2. The 2nd parameter to the -event parameter is a TCBODY
    There are 4 examples:
    1. The TCLBODY is a simple string that happens to be a proc name
    2. The TCLBODY is several simple commands seperated by semicolons
    3. The TCLBODY is a multi-line {curly-brace} quoted string
    4. The TCLBODY is a string with variables that get expanded.

    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}

25.6.4 Global Variables

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]
     }

25.7 Other Tcl Hacks

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"
     }