Introduction
Microsoft recently released Roslyn CTP, which previews the upcoming features of C# and VB.NET. Roslyn CTP is a pretty exciting release, and it opens up a lot of possibilities
for C# and VB.NET programmers. Roslyn provides language services and APIs on top of .NET’s compiler services, and this will enable .NET developers to do a lot more
things – including using C# and VB.NET as scripting languages, use a compiler as a service in your own applications for code related tasks, develop better language and IDE extensions, etc.
Also, Roslyn provides a lot of possibilities for developers to write code analysis and manipulation tools around the Visual Studio IDE, and provides APIs that’ll enable
you to develop Visual Studio enhancements pretty quickly. The Roslyn APIs will have full fidelity with C# and VB for syntax, semantic binding, code emission, etc. – and you’ll soon
see a lot of language bending around C# and VB.NET, and possibly new DSLs and a lot of meta programming ideas.
Roslyn has mainly four API layers:
- Scripting APIs
- Provides a runtime execution context for C# and VB.NET. Now you can use C#/VB.NET in your own applications as a scripting language.
- Compiler APIs
- For accessing the syntax and semantic model of your code.
- Workspace APIs
- Provides an object model to aggregate the code model across projects in a solution. Mainly for code analysis and refactoring around IDEs like Visual Studio,
though the APIs are not dependent on Visual Studio.
- Services APIs
- Provides a layer on top of Visual Studio SDK (VSSDK) for features like Intellisense, code formatting, etc.
In this post, we’ll have a sneak peak towards the Scripting APIs and Compiler APIs.
Scripting APIs
Some time back, I posted about how to use the Mono C# Compiler as a Service
in your .NET applications to leverage C# as a scripting language. Thanks to Roslyn, now we’ll have a first class Scripting API soon for .NET.
The Roslyn.Scripting.*
namespace provides types for implementing your own scripting sessions, using C# and VB.NET. You can create a new
scripting session using the Session.Create(..)
method. The Session.Create
method can also accept a Host
object, and the methods
of the Host
object will be directly available to the runtime context.
To execute some code, you can create an instance of a ScriptEngine
by providing the required assembly references and namespace import information,
and invoke the Execute
method of the Scripting Engine. To demonstrate how this works, let us create a simple ScriptingHost
which wraps a scripting session.
For demonstration, we’ll also create a ScriptedDog
class, and the purpose of this example is to create dogs and train them through the scripting environment.
Once you install Roslyn, create a new Console Application, and here is the code that demonstrates a simple scripting environment:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using Roslyn.Scripting;
using Roslyn.Scripting.CSharp;
namespace ScriptingRoslyn
{
public class OurDog
{
private string _name = string.Empty;
public OurDog(string name)
{
_name = name;
}
public string Name
{
get { return _name; }
}
public void Bite(OurDog other)
{
Console.WriteLine("{0} is biting the tail of {1}",
_name, other.Name);
}
public void Walk()
{
Console.WriteLine("{0} is Walking", _name);
}
public void Eat()
{
Console.WriteLine("{0} is Eating", _name);
}
}
public class ScriptingHost
{
private ScriptEngine engine;
private Session session;
public OurDog CreateDog(string name)
{
return new OurDog(name);
}
public ScriptingHost()
{
session = Session.Create(this);
engine = new ScriptEngine(new Assembly[]
{
typeof(Console).Assembly,
typeof(ScriptingHost).Assembly,
typeof(IEnumerable<>).Assembly,
typeof(IQueryable).Assembly
},
new string[]
{
"System", "System.Linq",
"System.Collections",
"System.Collections.Generic"
}
);
}
public object Execute(string code)
{
return engine.Execute(code, session);
}
public T Execute<T>(string code)
{
return engine.Execute<T>(code, session);
}
}
class Program
{
static void Main(string[] args)
{
var host = new ScriptingHost();
Console.WriteLine("Hello Dog Trainer!! Type your code.\n\n");
string codeLine = string.Empty;
Console.Write(">");
while ((codeLine = Console.ReadLine()) != "Exit();")
{
try
{
var res = host.Execute(codeLine);
if (res != null)
Console.WriteLine(" = " + res.ToString());
}
catch (Exception e)
{
Console.WriteLine(" !! " + e.Message);
}
Console.Write(">");
}
}
}
}
I assume the code is self-explanatory. We are creating a Host
class where we wrap the session and execute the code using the ScriptEngine in that session. A very simple REPL
example - so, now let us go and train the dogs.
You can find that we are invoking the CreateDog
method within our Host
class to create the dogs, and then we are training them – though I’m not sure if we really
need to train them to bite each other. Anyway, I hope the idea is clear.
Compiler APIs
Compiler APIs have object models for accessing the syntax and semantic models of your code. Using compiler APIs, you can obtain and manipulate syntax trees.
The common syntax APIs are found in the Roslyn.Compilers
and the Roslyn.Compilers.Common
namespace, while the language specific syntax APIs are found
in Roslyn.Compilers.CSharp
and Roslyn.Compilers.VisualBasic
.
‘Syntax’ is the grammatical structure whereas ‘Semantics’ refers to the meaning of the vocabulary symbols arranged with that structure. If you consider English,
"Dogs Are Cats" is grammatically correct, but semantically it is nonsense.
Building syntax trees and accessing the semantic model
Roslyn provides APIs for building syntax trees, and also for semantic analysis. So let us write some code that'll parse a method and create a syntax tree.
It also shows how to get the method symbol from the semantic model of our syntax tree:
SyntaxTree tree = SyntaxTree.ParseCompilationUnit
(@"class Bar {
void Foo() { Console.WriteLine(""foo""); }
}");
MethodDeclarationSyntax methodDecl = tree.Root
.DescendentNodes()
.OfType<ClassDeclarationSyntax>()
.First().ChildNodes().OfType<MethodDeclarationSyntax>().First();
Compilation compilation = Compilation.Create("SimpleMethod").AddSyntaxTrees(tree);
var model = compilation.GetSemanticModel(tree);
Symbol methodSymbol = model.GetDeclaredSymbol(methodDecl);
Console.WriteLine(methodSymbol.Name);
In the above example, you can easily figure out how we are parsing the code to a SyntaxTree
, getting the semantic model associated with that syntax tree,
and then looking up for information in the semantic model. In this case, we are getting the method symbol that corresponds to the first method declaration within the first class
declaration in the syntax tree. I.e., DescendentNodes().OfType<ClassDeclarationSyntax>().First()
of the root node gives us the first
class declaration node, and ChildNodes().OfType<MethodDeclarationSyntax>().First()
gives us the first method declaration node within that class declaration.
To keep the point short, traversing the syntax tree using the object model is pretty easy.
The symbols we obtain from the semantic model can be used for a wide range of scenarios, including code analysis.
A bit more about syntax trees
Instead of parsing the entire code to a syntax tree, you can also parse code and for child syntax nodes – for example, you can parse a statement to a StatementSyntax
.
StatementSyntax statement=Syntax.ParseStatement("for (int i = 0; i < 10; i++) { }");
Syntax trees hold the entire source information in full fidelity - which means it contains every piece of the source information. Also, they can be round
tripped to form the actual source code back from the syntax tree or the node. Syntax trees and nodes are immutable and thread safe. However, it is possible
to replace a node entirely in the current syntax tree by using the ReplaceNode
function to create a tree with the old node replaced with the new node. For now, that is it.
Be warned that this Roslyn release is just a CTP, and a lot of features like Dynamic, Expression Trees, etc., are not yet supported. Also, a couple of things
might change in the final release. See this post on the limitations:
http://social.msdn.microsoft.com/Forums/en-US/roslyn/thread/f5adeaf0-49d0-42dc-861b-0f6ffd731825.
Enjoy coding, and follow me on Twitter.