Tcl Template Parser -

Started from the need to create lots of files from a templates and some variable definitions.

Now it is several things:

  • Still a versatile template parser
  • A compact (254 sloc) wrapper for Tcl applications, with command-line and configuration-file parser
  • An example of a real, live program written with Compact Code Formatting Style

Look at the (hopefully soon) comprehensive documentation if you want to use the program.

On this page I want to present some concepts used when coding TTP.

Command-line parsing with active tokens and recursion

The initial command-line parser I used was a simple while loop; it makes use of the "shift" proc, which pops off an element of a list.

 set params {}
 set cl $argv
 while {[llength $cl]} {
     set t [pop cl]
     switch -- $t {
         -h {usage; exit 0}
         -d {set ::loglevel [pop cl]}
         default {lappend params $t}}}

This is a very simple-minded parser that requires all options and parameters to be separated by whitespace, consumes all arguments of the command line, and pushes everything that is not defined as option - arguments, hopefully - on a list for later usage. 'pop' is made in such a way that it always pops off an empty list from an empty list.

Since 'while is bad' I wanted to do better and recurred to the intrinsic list processing feature of 'proc':

  • every option is defined as a proc, consuming exactly the arguments it needs
  • argv is processed recursively; there will not be thousands of switches on my command lines

Here comes the whole parser:

 proc options args {if [llength $args] {eval $args}}

It is started with:

 eval options $argv

Very self-describing, isn't it? It has no syntax though, so let's make one:

 proc -h args {usage; exit 0}
 proc -d loglevel {set ::loglevel $loglevel; eval options $args}

If we 'eval options -d 4 -h' the following will happen:

  • '-d' is called with parameters '4 -h', which sets ::loglevel to 4 and calls 'eval options -h'
  • '-h' is called, which prints out the 'usage' message and goes home

But how about parameters? They would be 'eval'ed and trigger an error: invalid command name "..."\nwhile evaluating '...'.

This is the default behaviour of the unknown function and not what we want, so let's redefine it. 'unknown' is called with the (unknown) commandname and the (variable) argumentlist. The commandname is our parameter from the command line:

 proc unknown {param args} {
     lappend params $param;  eval options $args}

So Tcl handles

lets put it together:

 set params {}
 proc -h args {usage; exit 0}
 proc -d {loglevel args} {set ::loglevel $loglevel; eval options $args}
 # more options here ...

 proc options args {if [llength $args] {eval $args}}
 proc unknown {param args} {
     lappend ::params $param;  eval options $args}

 eval options $argv

While this is not really shorter than the while loop, it has some error handling built in, e.g., if you specify '-d' without a following argument, Tcl personally rants: 'wrong # args: should be "-d loglevel args"'

If you specify 'set ::loglevel 8' on the command line, you get it set to 8, because the 'parameter' set is not unknown. So if you want to avoid Tcl code 'injection' from the command line you better run the parser inside a slave interpreter with only the -option commands.

The command-line parser in TTP is much more elaborate, to account for some of such pitfalls. It also makes use of code introspection to generate the 'usage' message automatically. How this? In the two above examples of -option procedures you see that the argument list (minus 'args') shows as directly the number (and connotation) of the arguments of a command-line option. We can get a list of options with 'info procs -*' and the argument list of a specific option with e.g. 'info args -d'.

Tcl does not supported procedure comments like, e.g., in some lisp dialects; however you could cope with this by defining your options this way:

 proc -h args {# give usage message
     usage; exit 0}

Then you can get to the option comment with 'lindex [split [info body -h] \n] 0'; of course you need to strip off the '# ' in front of the comment. From all this you can create lines like:

 -h          .. give usage message
 -d loglevel .. set loglevlel

automatically. If you add an option, you automatically get it added to the usage message.


Category Application | Category Template