Here is a simple example of writing an Attribute Macro in boo. I have created a macro that you can use to ensure that parameters are not null and raise a ArgumentNullException if they are. The interesting thing to note about this is that if you were to do this in C# you would need to use a factory method to inject these methods as aspects at runtime. With Boo macros you are actually changing the code at build time, which has both positive runtime performance implications and positive architectural changes since you do not need a factory method.
Here is an example of how you might use such an attribute:
namespace MacroExample
import System
import Macros
class Example:
[Ensure(example)]
def DoSomething(example as string):
print example.ToUpper()
e = Example()
e.DoSomething(“testing!”)
e.DoSomething(null)
print “Press any key to continue . . . “
Console.ReadKey(true)
|
I have declared a class called Example with a single method DoSomething. On that method we have attached an Attribute called Ensure. If you were using standard Aspect Oriented Programming techniques you would need some custom code to inspect this attribute at runtime and build a dynamic type and method to wrap the logic of the ensure attribute.
In Boo, since my EnsureAttribute inherits from AbstractMacroAttribute the logic of the Attribute will actually be run at build time, not runtime. Note the parameter in the constructor, the ‘example’. There are no quotes around this because it is not a string. It actually comes into the constructor as a ReferenceExpression which I can use to match to the methods parameters.
Here is the ensure Attribute code:
namespace Macros
import System
import Boo.Lang.Compiler.Ast
[AttributeUsage(AttributeTargets.Method)]
class EnsureAttribute(Boo.Lang.Compiler.AbstractAstAttribute):
private _parameter as ReferenceExpression
public Parameter as ReferenceExpression:
get:
return _parameter
def constructor(parameter as ReferenceExpression):
_parameter = parameter
override def Apply(node as Node):
target = cast(Boo.Lang.Compiler.Ast.Method, node)
parameter as ParameterDeclaration
for p in target.Parameters:
if p.Name == _parameter.Name:
parameter = p
break
code = [|
if $(parameter) is null:
raise ArgumentNullException($(parameter.Name))
|]
target.Body.Insert(0, code)
|
One of the most amazing parts about constructing macros in Boo is the built in code templating features. If you notice towards the bottom of the example where we are assigning to the variable ‘code’ there is a special block wrapped in [| … |]. This is a special type of operator that tells Boo that whatever is inside of this block is to be parsed and returned as an AST (abstract syntax tree) node. This is a helper for replacing reliance upon manually constructing Boo CodeDom objects. You can see on the following line that this compiled code is being inserted into the body of the method this attribute has been applied to.
It is important to note that while we are affecting the body of the method that method knows about the class it is attached to and that class knows about all other classes in the assembly and so on. This is the difference in power between Boo macros and C++ macros.
You must log in to post a comment.