We've been using C# for a couple of years now, and are getting tired of the verbosity. Especially tired of copy/pasting and
changing a couple of identifiers, and I imagine many other people are, too. After seeing some of the macro capabilities
of Lisp, we got jealous. After some googling and browsing, I ran across
Ned Batchelder's
python-based code generation tool,
Cog.
Cog lets you build ad-hoc code generators in python, in your source code files. Here's an example from Ned's site:
/*[[[cog
import cog
fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
for fn in fnames:
cog.outl("void %s();" % fn)
]]]*/
void DoSomething();
void DoAnotherThing();
void DoLastThing();
//[[[end]]]
You have some python code in a
[[[cog ]]] block, which gets executed to create code in whatever desired language.
This lets us write macros in python for C#.
I use Cog in concert with Nant and Visual Studio for working on C# projects. I have a nant build script to run Cog on all appropriate file types,
and then compile my visual studio solution using Nant. I build the python macros in Xemacs, testing them using the interpreter built in there.
It's a bit a rube-goldberg contraption, but it works pretty well.
-
Download and install
Python,
Cog,
Nant,
and Xemacs.
- Make another project for the python templates/macros.
- Add a "default.build" to the solution (Right click on the Solution node in the tree, and choose Add Item)
-
Also Add the cog.py file from your python/Scripts folder. The Cog install should've put it there. There's probably a better way to do this, but
having the cog.py here makes the nant build file easy.
-
Put the following XML in your default.build file:
<project name="default" default="debug" basedir=".">
<!-- This build target loops through and runs cog on every .cs, .js, and .xul file in the solution. -->
<target name="codegen">
<foreach item="File" property="filename">
<in>
<items>
<include name="**.cs" />
<include name="**.js" />
<include name="**.xul" />
</items>
</in>
<do>
<exec program="python">
<arg value="cog.py" />
<arg value="-r" />
<arg value="${filename}" />
</exec>
</do>
</foreach>
</target>
<!-- This build target compiles the solution in "release" mode, and calls the codegen target automatically. -->
<target name="release" description="compiles in release mode" depends="codegen" unless="${target::has-executed('release')}">
<solution configuration="release" solutionfile="Ryan.sln" />
</target>
<!-- This build target compiles the solution in "debug" mode, and calls the codegen target automatically. -->
<target name="debug" description="compiles the solution, then runs the tests" depends="codegen" unless="${target::has-executed('debug')}">
<solution configuration="debug" solutionfile="Ryan.sln" />
</target>
</project>
Change the Ryan.sln to be the name of your solution.
-
Your solution should look something like this:

(Mokeys and BayesianCat are the projects I'm generating with)
-
Now, open up Xemacs, and start making files in your Templates project. Put an __init__.py in each folder in the Templates project.
This specifies that folder as a package, and lets you refer to it in code elsewhere easily.
-
When using them
[[[cog]]] blocks, import your templates and go at it.
/*[[[cog
import cog
from Templates.BayesianCat import Bayesian
cog.outl(Bayesian.makeJS())
]]]*/
//[[[end]]]
-
Open a command prompt to your solution root, and run
nant to generate and build your solution.
I think the setup I have is far from ideal, and am still figuring out good python macros, and good ways to use them. Here are a
few examples.
Making some XUL
def makeUI():
return ui.window([includes.includejs('Bayesian.js'),
ui.groupbox('Input', [ui.labelbox('message', 'Enter the message to be scored', True),
ui.button('Score', onclick='Score();'),
ui.labelbox('category', 'Create a new category'),
ui.button('Add')])])
This puts together some textboxes and buttons, using a
ui module I defined elsewhere.
It looks a little Lispy, and ends up being 12 lines of valid XML.
Making some C#
def fileNameWithCheck(fileName, comment='the file, with existence check on setting.'):
return '''
protected string _%(Name)sFileName;
/// <summary>
/// %(Comment)s
/// </summary>
public string %(Name)sFileName {
get{ return _%(Name)sFileName;}
set {
if(File.Exists(value)) _%(Name)sFileName = value;
else throw new FileNotFoundException(\"The file \" + value + \" was not found!\");
}
}''' % {'Name' :fileName, 'Comment':comment}
This makes an accessor for a filename, and does the existence check against the file system.
As with everything, I may be doing it all wrong, but this jury-rig is certainly faster than pure C#. Once I figure out a way to have a nicer library
of templates and macros, I'll post about it.
UPDATE: Apparently this is getting a lot of traffic, which is pretty neat, although I'm a bit nervous about the minds frequenting
lambda-the-ultimate looking over my shoulder. I just wanted to add that I got a
much more efficient Nant target that should be used in place of the
codegen target above, and I found
a decent way to use a central library of templates and macros without mucking with
sys.path.