Version 0 of Why can I not place unmatched braces in Tcl comments

Updated 1999-06-29 20:33:52

If a brace - that is to say, the character { - appears in a Tcl comment inside a proc, the Tcl parser will behave in a way that you may not expect.

The # character is not a special preprocessor directive (as comments are in C/C++). In Tcl, comments act a lot like a command. If a command starts with the character #, then the rest of the line is ignored.

When you define a proc in Tcl, you are actually running the proc command, and it expects three arguments: the name of the procedure, a list of procedure arguments and a procedure body. Both the argument list and the procedure body have to be properly formed strings, and they are usually quoted with braces. You can have nested quotes within these strings, but you can't have extra open braces or unbalanced quotation marks. So as far as the proc command is concerned, this is a perfectly valid declaration.

     proc foo { args } {
          puts "executing procedure foo"
          # comment out this code block {
               foreach a {$args} {
                    puts "argument: $a"
               }
          }
     }

Note that procedure body is a well formed Tcl string. All the nested braces balance. The proc command doesn't actually do anything with the procedure body. That happens later when you execute the procedure.

When you execute the procedure foo, Tcl recognizes the # comment command, and ignores the rest of the characters on that line including the trailing open-brace. It then processes the foreach command, which expects four arguments, one of which is a multi-line string quoted by braces. It then attempts to process the closing brace as a command, and fails.


Commenting out Blocks of Code

You may have been trying to comment out a block of code, like this

     # comment out this block {
     proc foo {} {
          blah ; blah ; blah
     }
     }

but that doesn't work because of the unmatched braces in the comment. The most thorough way to comment out that block of code is to place a # at the beginning of each line. But that is a lot of work. Instead, if your block is well-formed, parsable, Tcl code, remove the code with an if or proc command, like this:

     #  comment out this block using 'if'
     if 0 {
     proc foo {} {
          blah ; blah ; blah
     }
     }

or

     #  comment out this block using 'proc'
     proc donteverrunme {} {
     proc foo {} {
          blah ; blah ; blah
     }
     }

Comments from Paul Duffin, June 23, 1999

Not quite right. The following works

        proc a {} "
                # comment with unbalanced }
        "

but this doesn't

        proc a {} {
                # comment with unbalanced }
        }

And conversely this works

        proc a {} {
                # comment with unbalanced "
        }

but this doesn't

        proc a {} "
                # comment with unbalanced "
        "

Also the following doesn't work either

        proc a {} {
                puts "}"
        }

The rules:

  1. Parsing of a string only occurs when that string is evaluated and is lazy, which means that it does not parse the contents of words. That job is up to the particular command. (*)
  2. When parsing a {} enclosed word all characters apart from {} and \ are ignored.
  3. When parsing a "" enclosed word substitutions occur for $... and and \... but {} have no significance.
  4. When parsing a command if the command starts with a # then all characters up until the end of the line (or end of the script, whichever comes first) are ignored. This means that no substitutions are done in the comment.

These rules are relatively easy to follow in isolation but the problems come about when they are nested.

Take for instance the following code.

        proc a {a b} {
            if {$a < 0} {
                puts "Negative \{"
            } elseif {$a > 0} {
                puts "Positive \}"
            } else {
                puts "Zero {}"
            }
        }

When the proc command is evaluated the parser has split its arguments into the following words (the | are delimiters and as such are not part of the words).

        1: |a|
        2: |a b|
        3: |
            if {$a < 0} {
                puts "Negative \{"
            } elseif {$a > 0} {
                puts "Positive \}"
            } else {
                puts "Zero {}"
            }
        |

The parser did not look inside word 3 because that is up to the command and so it only used rule 2.

(*) The byte compiler does invalidate this slightly as it 'knows' how to parse certain commands and therefore will parse before evaluation.


The [switch] command

Watch out for comments inside switch commands, since the switch command does not itself interpret # as anything other than an ordinary character. Thus, you must make sure that they are only ever put inside the body parts, as otherwise you will end up with either wierd parsing or an out-and-out error.

Thus, the following code is wrong:

      switch $foobar {
          # Match a first
          a { puts "Matched a" }
          # Match b second
          b { puts "Matched b" }
      }

This is because it is equivalent to (i.e. precisely the same as):

      switch $foobar {
          # Match
          a first
          a { puts "Matched a" }
          # Match
          b second
          b { puts "Matched b" }
      }

Which is the same as:

      switch $foobar {
          # Match
          a first
          b second
      }

Not what was intended! The following is possibly even worse (depending on your point of view...)

      switch $foobar {
          #Match a first
          a { puts "Matched a" }
          # Match b second
          b { puts "Matched b" }
      }

This is equivalent to:

      switch $foobar {
          #Match a
          first a
          { puts "Matched a" } #
          Match b
          second b
          { puts "Matched b" }
      }

In this case, the body of the switch has an unpaired word, and so the switch command moans conspicuously. The correct way of writing the above is:

      switch $foobar {
          a {
              # Match a first
              puts "Matched a"
          }
          b {
              # Match b second
              puts "Matched b"
          }
      }

DKF


What other surprises do we want to document in this area?

Well, here's another case with similar troubles -- JC

      array set config {
          # this comment is messing up things ...
          path /usr/local/bin
          user admin
      }