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:
proc add_members {} { global attributes types foreach attribute $attributes { set type $types($attribute) puts " $type ${attribute}_;" } }
(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.
Here’s what the code currently looks like:
proc each_attribute { block } { global attributes types foreach attribute $attributes { uplevel [list set attribute $attribute] uplevel [list set type $types($attribute)] uplevel $block } } proc add_members {} { each_attribute { l " $type ${attribute}_;" } }
Certainly the new definition of add_members
is a lot shorter!
Some thoughts:
- I like using
uplevel
! For people who don’t know TCL, curly braces are a string quoting construct, so the block aftereach_attribute
in the body ofadd_members
is actually a string. Anduplevel
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 writingeach_attribute { attribute type } { ... }
over and over again; why force people to constantly repeat those variable names? (Especially since a call toeach_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
Post Revisions:
There are no revisions for this post.
Welcome to programming with Lisp-style macros! Once you’ve gone macro, you never go back-ro.
2/18/2008 @ 4:47 pm