T4 Text Transformation in MetaSharp

by justin 28. May 2010 17:53

Previously in MetaSharp I had a custom Templating engine for textual transformations. It was a custom grammar and was pretty simple. It worked well and all was good but it was becoming a maintenance burden for me and, I think, turned people off to MetaSharp more than attracted them. So I decided to spend a little time and try to integrate T4 into my templating library as a replacement.

It was actually reasonably simple to accomplish. The hardest part, of course, was working with AppDomains. T4 is a dynamic tool living in a static world and as such it requires an AppDomain to do its magic so it can all be unloaded without a trace. Because of this T4 isn’t very amenable to input. Most examples I found online start with firing up your template then beginning by reading a file or creating a class that connects to a database. In my case I have already have my metadata and would like to pass it into a T4 template but it’s in memory only. The first thing you have to do is make your model Serializable.

Next you need to know about the “inherits” and the “hostspecific” attributes. You add something like this to the top of your template:

<#@ template language="C#" inherits="SongMetaTemplate" hostspecific="true" debug="true" #>

This is for my Song sample application. I then create a SongMetaTemplate class that knows how to cast the model into a Song enumeration.

public abstract class SongMetaTemplate : MetaTemplate
{
    public IEnumerable<Song> Songs
    {
        get { return ((IEnumerable<INode>)this.Model).Cast<Song>(); }
    }
}

Adding a hostspecific attribute to the template causes the template class that inherits from the SongMetaTemplate to also have a Host property which gets loaded up with the Model provided by my pipeline. The MetaTemplate knows how to fetch that host model for me.

public abstract class MetaTemplate : TextTransformation
{
    private object model = null;

    protected object Model
    {
        get
        {
            if (model == null)
            {
                dynamic metame = this;
                var metaHost = metame.Host as MetaSharpTextTemplatingHost;
                if (metaHost != null)
                    this.model = metaHost.Model;
            }

            return model;
        }
    }

    public override string TransformText()
    {
        return this.GenerationEnvironment.ToString();
    }
}

And that’s it! My template can now get a hold of the Song AST parsed from the DSL earlier in the pipeline. Here is an example template in its entirety:

<#@ template language="C#" inherits="SongMetaTemplate" hostspecific="true" debug="true" #>
<#@ assembly name="DynamicSongBuilder" #>
<#@ import namespace="DynamicSongBuilder" #>

<# foreach(var song in this.Songs) { #>
    <# foreach(var b in song.Bars) { #>
        <# foreach(var n in b.Notes) { #>
            play(<#=song.Duration #>, "<#=n.Key #>", <#=n.Octave #>);
        <# }
    }
} #>

The template code is C# but the code generated is actually for my own custom language. In this case it’s parsed into Linq expressions and compiled into a delegate using a context object with a play method. It’s then executed dynamically and also collected by the GC when dereferenced. I plan to replace the custom language also, but its proving difficult without a reasonable parser to replace it.

Tags:

.NET | C# | DSL | dynamic | MetaSharp | T4

Building Dynamic Types in C# using IronPython

by justin 15. April 2010 09:57

I have created a small routine that allows you to use the IronPython runtime to create dynamic Types without requiring you to write any Python code.

The normal use case for IronPython in C# requires you to provide the IronPython runtime with some source code files, which will essentially eval and return to you a context which you can interact with dynamically. With some help on the forums I was able to find a way to provide an IronPython AST to the runtime instead of source code. This will be very useful for MetaSharp, since I already have a lot of mechanisms for creating an AST I will only need to simply create a transformer that will change an existing AST into IronPython AST and you can suddenly compile any DSL into arbitrarily complex object hierarchies at runtime.

One of the reasons for this is that I want to create a language workbench tool, where you can experiment with your transformations dynamically without having to recompile in the traditional sense. Also, it just gives you more options at runtime. This should be very handy.

Here is an example of what I’m talking about. My example code is very simple, create a class that implements a simple .net interface then create an instance of that class and call it’s method. Here is the actual python version:

#sample.py
from ConsoleApplication4 import IExample

class Example(IExample):
    def Do(self):
        return "hello python"

Executing and calling this at runtime in C# is quite easy to do out of the box actually.

var runtime = Python.CreateRuntime();
runtime.LoadAssembly(typeof(IExample).Assembly);
dynamic python = runtime.UseFile("sample.py");
            
var example = (IExample)python.Example();
Console.WriteLine(example.Do());

The rub here though is that you need that python source file laying around somewhere. Or you could generate code, put it in a temp file and use that instead but nonetheless, the creation of the dynamic object is done by creating python code only. My problem was to figure out how to bypass most of the parsing semantics and to provide an AST directly to the runtime instead of code files. The AST could be created by a MetaSharp transform step.

So instead you can now do this:

var runtime = Python.CreateRuntime();
runtime.LoadAssembly(typeof(IExample).Assembly);

var iexampleImport = typeof(IExample).Import();

var classDefinition = Dlr.Class(
    "Example",
    iexampleImport.ToEnumerable(),
    Dlr.Function(
        "Do",
        Dlr.Self().ToEnumerable(),
        "hello python!".AsConstant().Return()));

dynamic python = runtime.Compile(
    iexampleImport,
    classDefinition);

var example = (IExample)python.Example();
Console.WriteLine(example.Do());

I won’t go into all of the extension methods I created (see link to source at the end) but suffice it to say that the Dlr.Class method is creating a ClassDefinition object. Passing these into the runtime.Compile method is where all of the magic happens.

Figuring out what to do in the Compile extension method was the real hard part. It’s not well documented and some of the side effects are downright bizarre, clearly the API was not intended to be (mis)used in this way.

Fortunately for you, all you have to do is download the file below and you’re off creating dynamic Types using the DLR!

Tags:

MetaSharp | dynamic | C# | IronPython

Coercing Types and Unloading Assemblies

by justin 3. March 2010 04:09

I was doing a little experimentation recently related to building dynamic assemblies in .NET. One thing I found interesting was that if you create an instance of a dynamic Type then use the “dynamic” keyword to consume it, your dynamic assembly will never be unloaded.

That sort of defeats the purpose if you ask me. I was back to using reflection to call my dynamic objects. Not fun.

So I whipped up a quick dynamic coercion routine. Basically what it does is takes any instance and any interface and builds a wrapper object that implements the interface and calls a matching method on the instance. The instance does not need to implement the interface or even know about it. The coercion method will give the appearance of casting the instance into a interface it doesn’t really implement.

var instance = CreateDynamicAdder();
var adder = instance.Dynamic().Coerce<IAdder>();

Dynamic() is an extension point for type System.Object. Coerce is a extension method:

public static T Coerce<T>(this IDynamicExtensionPoint extensionPoint)
{
    if (!typeof(T).IsInterface)
        throw new ArgumentException("Type T must be an interface.");

    if (extensionPoint == null)
        throw new ArgumentNullException("extensionPoint");

    var assemblyBuilder = Dynamic.CreateDynamicAssembly();
    var wrapperType = BuildWrapperType(
        assemblyBuilder, 
        typeof(T), 
        extensionPoint.Instance.GetType());
    return (T)Activator.CreateInstance(wrapperType, extensionPoint.Instance);
}

So in the first code snippet I get an object that has a method I want to call that I know looks like this:

int Add(int x, int y)

I don’t know the exact Type and I don’t have any interfaces I can cast it into, but I know that this method is there, possibly through documentation, convention or debugging. The Coercion routine will allow me to call dynamic objects with an illusion of strong typing.

So I create an interface that matches the signature I am expecting.

public interface IAdder
{
    int Add(int x, int y);
}

And coerce the instance into this interface. What’s interesting is that when I do it this way, both the dynamic assembly creating the adder as well as the dynamic assembly creating the wrapper are unloaded when I drop references to them.

static void Main(string[] args)
{
    var numberOfAssemblies = AppDomain.CurrentDomain.GetAssemblies().Count();
    Console.WriteLine("Starting assembly count: " + numberOfAssemblies);

    var instance = CreateDynamicAdder();
    var adder = instance.Dynamic().Coerce<IAdder>();
    int z = adder.Add(7, 11);
    Console.WriteLine("Add result: " + z);
    instance = null;
    adder = null;

    numberOfAssemblies = AppDomain.CurrentDomain.GetAssemblies().Count();
    Console.WriteLine("Assembly count before collect: " + numberOfAssemblies);

    GC.Collect();
    GC.WaitForPendingFinalizers();

    numberOfAssemblies = AppDomain.CurrentDomain.GetAssemblies().Count();
    Console.WriteLine("Assembly count after collect: " + numberOfAssemblies);

    Console.ReadKey(true);
}

Here is the resulting screen shot, proving the dynamic assemblies are unloaded.

image

Download the full sample here:

Tags: , ,

.NET | C# | dynamic

About Me

sweetest hat ever

I'm a software developer from Minnesota and this blog largely focuses on various technical concepts I am thinking about at the moment. I currently work for Microsoft in the St. Paul office of the Expression product group.

RecentPosts