We’re writing an IDL compiler at work, to generate C++ code from a file containing a list of attributes. We’re doing this in TCL, which turns out to be surprisingly easy and fun: just define some procedures that correspond to the syntax of your language, eval the contents of the file, and you’re off and running!

The syntax is pretty simple, looking like this:

attribute int foo
attribute string bar

(Well, that’s what it started out looking like; there have been some changes, as we extended the language to handle more cases.) Loading this invokes a procedure attribute that sticks the names and types of the attributes in some lists/arrays. And then we call some output code that does stuff like this:

(I’m not cutting and pasting that, I may have some syntax details wrong. So don’t treat this post as a TCL tutorial!)

But after sticking in “foreach attribute $attributes { ... }” in a few places, it looked like a refactoring opportunity: I should define a procedure each_attribute that iterates over the attributes and binds the attribute and type to a local variable.

I like using uplevel! For people who don’t know TCL, curly braces are a string quoting construct, so the block after each_attribute in the body of add_members is actually a string. And uplevel evaluates that string in the context of the calling function, letting us define new control structures.

At first, I went back and forth about whether each_attribute should take another argument, namely the names of the variables that we want to bind the attribute name and type to. Ultimately, though, that would have let to writing each_attribute { attribute type } { ... } over and over again; why force people to constantly repeat those variable names? (Especially since a call to each_attribute within the block of another such call doesn’t make sense.)

I do, however, feel guilty about injecting those variables directly into the calling scope: what I really want to do is create a new contour within the calling scope and have the new variables only defined there. Anbody know offhand how to do that in TCL? Can’t be too hard, I just need to look it up