Bending T4 to be a textual DSL host
The estimable MVP and T4 enthusiast, Kathleen Dollard has a new post where she's using T4's ability to spit arbitrary text as a host for a textual DSL.
She's set up a small DSL for describing contract interfaces for a MEF framework:
new Interface()
{
Name = "ISearchModelBase",
Scope = Scope.Public,
CompositionInfo =
{
new Property() {Name="TargetType", PropertyType="Type"}
},
Members =
{
new Property() {Name="DisplayName", PropertyType="string"},
new Property() {Name="DataName", PropertyType="string"}
}
}.Output()
Hosting this in T4 with a couple of lines of wrapper code spits out the following output, codifying her standard implementation pattern for MEF contracts and custom attributes:
Option Strict On
Option Explicit On
Option Infer On
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.ComponentModel.Composition
public Interface ISearchModelBase
Property DisplayName As string
Property DataName As string
End Interface
public Interface ISearchModelBaseComposition
Readonly Property TargetType As Type
End Interface
< MetadataAttribute() > _
< AttributeUsage(AttributeTargets.Class, AllowMultiple:=False) > _
public Class SearchModelBaseAttribute
Inherits ExportAttribute
Implements ISearchModelBaseComposition
Public Sub New( ByVal targetType As Type)
MyBase.New(GetType(ISearchModelBase))
_targetType = targetType
End Sub
Private _targetType As Type
Public Readonly Property TargetType As Type Implements ISearchModelBaseComposition.TargetType
Get
Return _targetType
End Get
End Property
End Class
Fascinating stuff, especially when you see that these days, populating such a structure in C#4.0 is such a snap with the new initializer syntax.
Peeking under the covers, at the attachment to Kathleen's blog entry, the code combines the DSL structure definition with the code output for VB in a way that's a bit too printf-ish for my personal taste, so I was tempted to T4 it up a bit.
The original template passes a StringBuilder down the call tree and builds code using AppendLine():
private void AppendOpen(System.Text.StringBuilder sb)
{
sb.AppendLine("Option Strict On");
sb.AppendLine("Option Explicit On");
sb.AppendLine("Option Infer On");
sb.AppendLine("");
sb.AppendLine("Imports System");
sb.AppendLine("Imports System.Collections.Generic");
sb.AppendLine("Imports System.Linq");
sb.AppendLine("Imports System.ComponentModel.Composition");
}
Instead of this, I wanted to use regular T4 syntax with a class feature block:
private void AppendOpen()
{
#>
Option Strict On
Option Explicit On
Option Infer On
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.ComponentModel.Composition
<#+
}
However, this code is inside a nested class, so the underlying Write statements needed by T4 aren't present. To get around this, I whipped up a trivial base class that supplies all of the necessary plumbing for T4 to be happy. This works because this part of T4 doesn't rely on a specific type, rather it just expects access to the members it needs and any class will do. In my implementation I simply delegate all of the calls back out to the main T4 template.
The only changes necessary to the DSL definition classes are to derive from my DslBase class and to add a trivial constructor to supply the main template to each DSL class instantiation:
public class Interface : DslBase
{
public Interface(Microsoft.VisualStudio.TextTemplating.TextTransformation outer) : base(outer)
{
}
I've attached the base class code and a modified version of Kathleen's template for you to play with here. Enjoy.
Technorati Tags: T4,Textual DSL