Monthly Archives: September 2009

Preprocessed Templates and DSLs

In this post I will explore a bit further how to use preprocessed text templates to generate code based on Domain Specific Language models.

The first thing you need (after installing VS2010 Beta, of course), is to download and install Microsoft Visual Studio 2010 DSL SDK Beta1, available here.

Step 1: Create a default Domain-Specific Language Designer

Select File | New | Project. Select Other Project Types | Extensibility and then Domain-Specific Language Designer. Use the default project name (eg. Language1). Click finish to create the project.

Step 2: Run the project and create a Preprocessed template

After the project is created, run it. A new instance of Visual Studio runs, opening the Debugging solution. Add a new Preprocessed Text Template named DslTextTemplate.tt. Copy the template code from the Language1Report.tt file. This just outputs the elements defined in the model.

Add new references to the project:

  • Company.Language1.Dsl
  • Microsoft.VisualStudio.Modeling.Sdk.10.0
  • Microsoft.VisualStudio.TextTemplating.10.0
  • Microsoft.VisualStudio.TextTemplating.VSHost.10.0
  • WindowsBase (there was a strange error about INotifyCollectionChanged)

Build the project.

Step 3: Add the test template

Add a new Text Template named DslTextTemplateTest.tt. Add the template content:

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".txt" #>
<#@ assembly name="C:\Users\MyUser\Documents\Visual Studio 10\Projects\Language1\Language1\Debugging\bin\Debug\Debugging.dll" #>
<#@ import namespace="Debugging" #>
<#
    DslTextTemplate t = new DslTextTemplate();
    t.Initialize();
    this.Write(t.TransformText());
#>

The important bit here is the call to Initialize(). The first time I ran the template without this line, there was a null reference error in the model. I went looking in the template generated class for any references to the model, and noticed that it is being loaded in the Initialize method:

this.examplemodelValue = Company.Language1.Language1SerializationHelper.Instance.LoadModel(serializationResult, this.Store, "C:\\Users\\MyUser\\Documents\\Visual Studio 10\\Projects\\Language1\\Language1\\Debugging" +
            "\\Sample.mydsl1", null, null, null);

The test template generates the expected output.

First impressions:

Preprocessed text templates seem an ideal tool to quickly create and deploy new code generators based on DSL models. However, there are a few things that don’t seem right. As you can see above, the path to the DSL model is hard coded in the Initialize method. As I usually say, Modeling and Code Generation are two separate concerns, so there should be a way to specify for which model I am generating code.

But there’s always a way, and the use of a partial class helps in working around this issue. Simply by creating another partial class with a new Initialize method with an argument specifying the model path:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Debugging
{
    public partial class DslTextTemplate
    {
        public void Initialize(string modelPath)
        {
            this.AddDomainModel(typeof(Company.Language1.Language1DomainModel));

            base.Initialize();
            if ((this.Errors.HasErrors == false))
            {
                Microsoft.VisualStudio.Modeling.Transaction examplemodelTransaction = null;
                try
                {
                    Microsoft.VisualStudio.Modeling.SerializationResult serializationResult = new Microsoft.VisualStudio.Modeling.SerializationResult();
                    examplemodelTransaction = this.Store.TransactionManager.BeginTransaction("Load", true);
                    this.examplemodelValue = Company.Language1.Language1SerializationHelper.Instance.LoadModel(serializationResult, this.Store,
                        modelPath, null, null, null);
                    if (serializationResult.Failed)
                    {
                        throw new Microsoft.VisualStudio.Modeling.SerializationException(serializationResult);
                    }
                    examplemodelTransaction.Commit();
                }
                finally
                {
                    if ((examplemodelTransaction != null))
                    {
                        examplemodelTransaction.Dispose();
                    }
                }
            }
        }
    }
}

The call to Initialize now looks like this:

t.Initialize("C:\\Users\\MyUser\\Documents\\Visual Studio 10\\Projects\\Language1\\Language1\\Debugging\\Sample.mydsl1");

Conclusion

The use of partial classes helps in redefining the necessary behavior to make things work with Preprocessed templates. However, a more elegant solution should be available, such as exposing a model path property or an overload to Initialize that takes this parameter. The next step is suggesting this to the VS team 🙂.