[boo]
MockingBoo with meta programming facilities
[ bamboo ] 09:50, Wednesday, 31 October 2007

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)


Comments

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









Remember personal info?