Wednesday, November 02, 2005

Meta programming with Cog

I've known about Cog since Ned first mentioned it in his blog and I've worked with it since I joined Kubi but it wasn't until recently that I sat down and applied it to my own work. The experience was enlightening.



If you don't already know, Cog is text processor that searches text files (source code) for special blocks containing Python code. Once it finds one of these blocks, it executes the Python code and inserts the results back into the text. The principle use for Cog is as a code generator - hence the name.



Given how I have seen Cog applied at Kubi and how the DRY principle that guides its use is generally described, I tended to think of it as a systemic tool for generating reams of glue code. But once I played with it a little I realized it's also great for little jobs as well.



Recently I was writing some code that worked with a bunch of bitmaps. Each bitmap needed to be loaded from a separate file and each one needed to be accessed independently via a offset into an array. The first pass through, I defined an array of string constants that defined the bitmaps file names and I defined another array that would hold the bitmaps and then a whole bunch of nicely named integers constants that mapped to the slots in the array where the bitmap would be loaded. The following shows basically what I'm talking about, but imagine a lot more bitmaps.



private const int foo_offset = 0;
private const int boo_offset = 1;
private const int moo_offset = 2;

private static readonly string[] BitmapNames
= new string[] {
"foo.bmp",
"boo.bmp",
"moo.bmp"};

private static Bitmap[] LoadedBitmaps;



Unfortunately, the next day after I wrote this all the bitmap file names changed. As I started the tedious task of renaming the filename constants and the integer constants to match the new filenames it dawned on me this would be a perfect problem for cog. I defined a single array in Python to describe the basic names and wrote a small amount of code to translate the simple list into both both the filename array and the access constants. The code looked like this:



/*[[[cog
import cog
bitmaps =(
"foo",
"boo",
"moo",
)
counter = 0
for name in bitmaps:
cog.outl("\tprivate const int %s_offset = %s;"
% (name, counter))
counter = counter + 1
cog.outl("")
cog.outl("\tprivate static readonly string[] BitmapNames
= new string[] {")
for name in bitmaps:
cog.outl('\t\t"%s.bmp",' % (name))
cog.outl('null};')
]]]*/


The above cog snippet generates the equivalent of the original code and eliminates the need to coordinate the offset with the slots in the array thus eliminating a tedious maintenance chore. You can go one step further as well and put the Python code in a function so you can use the exact pattern again without needing to duplicate the code.


The savings of using this technique may be small initially but it will grow over time. The more you use the pattern the more the saving accrue. It's really an addictive technique. Give it a try, I think you'll like it.

Post a Comment
 
The Out Campaign: Scarlet Letter of Atheism