|
[boo]
MockingBoo with meta programming facilities
[
bamboo
]
Oren talks about a simple but interesting macro to aid with mocking. I decided to see if and how the latest meta programming facilities I've been working on are actually useful. Here's the complete application, what do you think? namespace Adapter
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.Ast.Visitors
import Boo.Lang.Compiler.TypeSystem
import Boo.Lang.Compiler.MetaProgramming
class AdapterMacro(AbstractAstMacro):
def Expand(macro as MacroStatement):
if macro.Arguments.Count != 1 or not macro.Arguments[0] isa ReferenceExpression:
raise "adapter must be called with a single argument"
entity = NameResolutionService.Resolve(macro.Arguments[0].ToString())
raise "adapter only accept types" unless entity.EntityType == EntityType.Type
BuildType(macro, entity)
def GetModule(node as Node) as Boo.Lang.Compiler.Ast.Module:
return node.GetAncestor(NodeType.Module)
def BuildType(macro as MacroStatement, type as IType):
adapterInterface = [|
interface $("I" + type.Name):
pass
|]
adapter = [|
class $(type.Name + "Adapter")($adapterInterface):
theTarget as $(type.FullName)
def constructor(target as $(type.FullName)):
theTarget = target
|]
GetModule(macro).Members.Add(adapter)
GetModule(macro).Members.Add(adapterInterface)
for member in type.GetMembers():
AddMethod(adapter, adapterInterface, member) if member isa IMethod
BooPrinterVisitor(System.Console.Out).Visit(adapterInterface)
BooPrinterVisitor(System.Console.Out).Visit(adapter)
def AddMethod(adapter as ClassDefinition,
adapterInterface as InterfaceDefinition,
method as IMethod):
if not method.IsPublic: return
if method.IsStatic: return
if method.ReturnType.IsByRef: return
if method.ReturnType.IsArray: return
interfaceMethod = [|
def $(method.Name)() as $(method.ReturnType.FullName):
pass
|]
forwarder = interfaceMethod.CloneNode()
forwardInvocation = [| theTarget.$(method.Name)() |]
for param in method.GetParameters():
if param.IsByRef or param.Type.IsArray:
return
forwarder.Parameters.Add(
ParameterDeclaration(
Name: param.Name,
Type: SimpleTypeReference(param.Type.FullName)))
interfaceMethod.Parameters.Add(
ParameterDeclaration(
Name: param.Name,
Type: SimpleTypeReference(param.Type.FullName)))
forwardInvocation.Arguments.Add(ReferenceExpression(param.Name))
adapterInterface.Members.Add(interfaceMethod)
adapter.Members.Add(forwarder)
if method.ReturnType == TypeSystemServices.VoidType:
forwarder.Body.Add(forwardInvocation)
else:
forwarder.Body.Add([| return $forwardInvocation |])
code = [|
import Adapter
adapter int
print Int32Adapter(42) isa IInt32
|]
try:
module = compile(code, typeof(AdapterMacro).Assembly)
module.EntryPoint.Invoke(null, (null,))
except x as CompilationErrorsException:
print x.Errors.ToString(true)
One of my biggest frets over the work I've done in Boo is that the above code won't work with generics. You can't just do "someType.FullName" and get a valid typereference to it. Also, puzzled by the way you can use a Macro from within code compiled dynamically from the assembly which contains that macro. But I guess I shouldn't be. --Avish, October 31, 2007 01:16 PM
I guess I have a solution for the type.FullName thing. we can make IType provide a service to get a proper TypeReference out of it. --bamboo, October 31, 2007 01:21 PM
This is a little off-topic, but I was wondering what editor you normally use to develop .boo files. I am using SharpDevelop, but I find that it is difficult to go back to the previous indent level if I'm using spaces instead of tabs. I have to repeatedly hit backspace to go back instead of just hitting it once as I would expect. --Jack, November 1, 2007 02:15 PM
I use monolipse (a set of eclipse plugins I wrote) most of the time because it works on all of three platforms I'm constantly switching from/to. Unfortunately it's still very much in flux so the update site doesn't work. I should probably put a wiki page on how to set it up somewhere. :/ --bamboo, November 1, 2007 02:25 PM
Post a comment
|