Boo AstMacros explained
In this post I am going to explain how you write your own macros in Boo. Writing macros is a powerful way to use the compiler extensibility built into Boo. Macros in Boo actually let you create your own keywords which are resolved at compile time.
Before you read on, these posts might be useful to read:
To create a Macro, you simply have to create a class that inherits from AbstractAstMacro and overrides the Expand-method. In addition your class name must end with “Macro”.
Let’s demonstrate with my DevNull-macro:
1 2 3 4 5 6 | import Boo.Lang.Compiler import Boo.Lang.Compiler.Ast class DevNullMacro(AbstractAstMacro): override def Expand(macro as MacroStatement): pass |
How do you use it? In your code (in a different assembly than where DevNullMacro is situated) you can write something like this:
1 2 3 | def DoTheMath(x as int) as int: devNull: x>0 |
Here you send in a “Block” to the macro (”X>0″). You can actually also send arguments:
1 2 3 | def DoTheMath(x as int) as int: devNull x,10,"Hello": x>0 |
So, a Macro can take any number of arguments, and then it takes a block (which can be as big as you like).
Now why did I call this macro DevNull? I did it to demonstrate how the macros work. The point is if you choose to do nothing in the Expand method, nothing will happen. The compiler will actually remove the macro with its arguments and its Block. Meaning that since it is only our macro that handles the code inside the block sent into the macro we can send in whatever we’d like.
This code actually compiles:
1 2 3 4 | def DoTheMath(x as int) as int: devNull x,10,"Hello": x>0 I_can_write_whatever_here_actually |
Ok, so when the compiler hits a macro statement in the compiling code, it finds the macro implementation (in this case the DevNullMacro), and sends it both the arguments and the code block. The compiler then removes all that code, and continues.
(I know this might seem a little confusing. When I started learning Boo it helped quite a lot to look at the compiled code using a tool like Reflector. You should try to write this code and look at it. It will clear up a few things, believe me.)
So what is the point then? The point is that within the macro, you can add code to the code structure. You can of course add whatever you like, but normally you add something based on the input to the macro.
To demonstrate something useful, I will show you how to implement Design By Contract. I have previously demonstrated it in several talks I’ve held, but then I have implemented it as AstAttributes like this:
1 2 3 4 5 6 7 8 | class Account: public Balance as int [Require(amount>=0)] [Ensure(Balance == originalBalance + amount)] def Deposit(amount as int): originalBalance = Balance Balance += amount |
I will not go into details about this, but I’m pretty sure you will manage to do it yourself after reading my post about AstAttributes.
Now I want to implement Design By Contract as macros, in the same way it is implemented in Eiffel, the language that introduced it. I want to write my assertions like this:
1 2 3 4 5 6 7 8 9 10 11 12 | class Account: public Balance as int def Deposit(amount as int): requires: amount>=0 body: originalBalance = Balance Balance += amount ensures: Balance == originalBalance + amount |
We have three macros here: requires, body, and ensures. I’m going to show you in detail how to implement the requires-part of the code, then I’ll add the source for both body and ensures as well. When we hit the requires macro in the code, we’re going to replace it with some actual code that forces those requirements. So, where we find this code in our Deposit-method,
1 2 | requires: amount>=0 |
we want, at compile time, to put something like this to the output assembly:
1 2 | if not (amount>=0): raise Exception("Some suitable error message") |
To add code where the macro statement is situated, we simply return a Statement from our Expand-method. What we want to make is a Block (which is a derived type of Statement):
1 2 3 | override def Expand(macro as MacroStatement) as Statement: blockToReturn = Block() return blockToReturn |
Still nothing happens, since we’re just returning an empty Block. We have to fill the return block with something. So for each assertion (which turns out as Statements) in our code, we want to assert it. Let’s loop all our statements:
1 2 3 4 5 6 7 8 9 | override def Expand(macro as MacroStatement) as Statement: blockToReturn = Block() for statement in macro.Block.Statements: expression as BinaryExpression = TryMakeBinaryExpression(statement) blockToReturn.Add( CreateAssertblockFromBinaryExpression(expression)) return blockToReturn |
In addition to the looping, which is pretty straight forward, we’re doing two new things here. We’re trying to turn every statement into a BinaryExpression, and we’re creating blocks for each BinaryExpression.
Let’s look at our TryMakeBinaryExpression first. The point is that if we want to assert something, we have to make sure they resolve to either true or false. First we need all the Statements to be ExpressionStatements. If you for instance had written an if-statement in you macro like this:
1 2 3 | requires: if 1==1: do_something |
This will not be an ExpressionStatement, it is an IfStatement (you should look into the Boo class-hierarchy, use Visual studio or Reflector).
So, when we have assured us it is all ExpressionStatements we have to deal with, we also want to ensure they are all BinaryExpressions. They all have to resolve into either true or false. Both of these checks are run here:
1 2 3 4 5 6 7 8 9 10 | def TryMakeBinaryExpression(statement as Statement) as BinaryExpression: expression = statement as ExpressionStatement if expression is null: RaiseNotBinaryException(statement.ToString()) binaryexpression = expression.Expression as BinaryExpression if binaryexpression is null: RaiseNotBinaryException(expression.Expression.ToString()) return binaryexpression |
Ok, now we either have a BinaryExpression or it has already failed. Let’s make it into a block:
1 2 3 4 5 6 7 | def CreateAssertblockFromBinaryExpression(expression as BinaryExpression) as Block: exceptionText = "Voilated precondition: " + expression.ToString() return [| block: if not $(expression): raise Exception($exceptionText) |].Block |
Here we’re using quasiquotations again. It’s understandable and easy to both read and write.
And that’s the complete macro. So now for every BinaryExpression written, the macro will assert its correctness. Here’s the complete code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | import System import Boo.Lang.Compiler import Boo.Lang.Compiler.Ast class RequiresMacro(AbstractAstMacro): override def Expand(macro as MacroStatement) as Statement: blockToReturn = Block() for statement in macro.Block.Statements: expression as BinaryExpression = TryMakeBinaryExpression(statement) blockToReturn.Add( CreateAssertblockFromBinaryExpression(expression)) return blockToReturn def CreateAssertblockFromBinaryExpression(expression as BinaryExpression) as Block: exceptionText = "Voilated precondition: " + expression.ToString() return [| block: if not $(expression): raise Exception($exceptionText) |].Block def TryMakeBinaryExpression(statement as Statement) as BinaryExpression: expression = statement as ExpressionStatement if expression is null: RaiseNotBinaryException(statement.ToString()) binaryexpression = expression.Expression as BinaryExpression if binaryexpression is null: RaiseNotBinaryException(expression.Expression.ToString()) return binaryexpression def RaiseNotBinaryException(typeInfo as String): raise Exception("Not a binary expression: " + typeInfo) |
The other two macros, BodyMacro and EnsuresMacro are listed here:
1 2 3 4 5 6 7 8 | import System import Boo.Lang.Compiler import Boo.Lang.Compiler.Ast class BodyMacro(AbstractAstMacro): override def Expand(macro as MacroStatement) as Statement: return macro.Block |
Here we only need to return the actual code written within the macro.
And here’s the EnsuresMacro:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import System import Boo.Lang.Compiler import Boo.Lang.Compiler.Ast class EnsuresMacro(AbstractAstMacro): override def Expand(macro as MacroStatement): ancestor as Method = macro.GetAncestor(NodeType.Method) oldBody as Block = ancestor.Body ensuresBody as Block = RequiresMacro().Expand(macro) newBody = [| block: try: $oldBody ensure: $ensuresBody |].Block ancestor.Body = newBody |
Here we take all the code in the method containing the macro, and surrounds it with a try-clause to ensure that we always run the ensures-part of the code. Notice also that we’re using the RequireMacro, since it creates the assert-block exactly the way we want it.
A helping hand:
When you explore this, I actually advice you to write some of your macro code in C#, as it gives you a lot more intellisense-help, then when you have found out what you want, write them back in Boo, as it is often much more readable, especially with the nice quasiquotation which you do not have in C#.
Hope it helps!
- Tore Vestues


I have enjoyed your articles, and would like to try these out for myself. However I get a compiler error:
Unexpected token: |. (BCE0043) – …\RequiresMacro.boo:19,17
Referring to the quasiquotation. Any ideas as to what is wrong.
December 30th, 2008 at 4:17 (220)@Wyatt:
If you post the method I can take a look at it.
Remember that Boo is Indent-sensitive, it might have something to do with that.
December 30th, 2008 at 8:38 (401)Sorry, I assumed I was using the latest version of SharpDevelop. Upgrading to the 3 Beta did the trick.
Your articles really help explain what is going on. But I find myself wanting to more about how the quasiquotation works. I hope you write more about that in the future.
Thanks again.
December 30th, 2008 at 19:59 (874)Aw, this was a really nice post. In idea I wish to put in writing like this additionally – taking time and actual effort to make an excellent article… but what can I say… I procrastinate alot and in no way appear to get one thing done.
February 1st, 2011 at 7:40 (361)Really good site, where did you come up with the knowledge in this piece of writing? Im glad I found it though, ill be checking back soon to see what other articles you have.
February 7th, 2011 at 23:40 (028)I can tell that you are not using a standard WP theme here. Which one is it.
June 11th, 2011 at 22:52 (994)hi nice tips thanks
August 14th, 2011 at 20:35 (899)