Search code examples
c#syntactic-sugarlanguage-construct

Is there a way to implement custom language features in C#?


I've been puzzling about this for a while and I've looked around a bit, unable to find any discussion about the subject.

Lets assume I wanted to implement a trivial example, like a new looping construct: do..until

Written very similarly to do..while

do {
    //Things happen here
} until (i == 15)

This could be transformed into valid csharp by doing so:

do {
    //Things happen here
} while (!(i == 15))

This is obviously a simple example, but is there any way to add something of this nature? Ideally as a Visual Studio extension to enable syntax highlighting etc.


Solution

  • Microsoft proposes Rolsyn API as an implementation of C# compiler with public API. It contains individual APIs for each of compiler pipeline stages: syntax analysis, symbol creation, binding, MSIL emission. You can provide your own implementation of syntax parser or extend existing one in order to get C# compiler w/ any features you would like.

    Roslyn CTP

    Let's extend C# language using Roslyn! In my example I'm replacing do-until statement w/ corresponding do-while:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Roslyn.Compilers.CSharp;
    
    namespace RoslynTest
    {
    
        class Program
        {
            static void Main(string[] args)
            {
    
                var code = @"
    
                using System;
    
                class Program {
                    public void My() {
                        var i = 5;
                        do {
                            Console.WriteLine(""hello world"");
                            i++;
                        }
                        until (i > 10);
                    }
                }
                ";
    
    
    
                //Parsing input code into a SynaxTree object.
                var syntaxTree = SyntaxTree.ParseCompilationUnit(code);
    
                var syntaxRoot = syntaxTree.GetRoot();
    
                //Here we will keep all nodes to replace
                var replaceDictionary = new Dictionary<DoStatementSyntax, DoStatementSyntax>();
    
                //Looking for do-until statements in all descendant nodes
                foreach (var doStatement in syntaxRoot.DescendantNodes().OfType<DoStatementSyntax>())
                {
                    //Until token is treated as an identifier by C# compiler. It doesn't know that in our case it is a keyword.
                    var untilNode = doStatement.Condition.ChildNodes().OfType<IdentifierNameSyntax>().FirstOrDefault((_node =>
                    {
                        return _node.Identifier.ValueText == "until";
                    }));
    
                    //Condition is treated as an argument list
                    var conditionNode = doStatement.Condition.ChildNodes().OfType<ArgumentListSyntax>().FirstOrDefault();
    
                    if (untilNode != null && conditionNode != null)
                    {
    
                        //Let's replace identifier w/ correct while keyword and condition
    
                        var whileNode = Syntax.ParseToken("while");
    
                        var condition = Syntax.ParseExpression("(!" + conditionNode.GetFullText() + ")");
    
                        var newDoStatement = doStatement.WithWhileKeyword(whileNode).WithCondition(condition);
    
                        //Accumulating all replacements
                        replaceDictionary.Add(doStatement, newDoStatement);
    
                    }
    
                }
    
                syntaxRoot = syntaxRoot.ReplaceNodes(replaceDictionary.Keys, (node1, node2) => replaceDictionary[node1]);
    
                //Output preprocessed code
                Console.WriteLine(syntaxRoot.GetFullText());
    
            }
        }
    }
    ///////////
    //OUTPUT://
    ///////////
    //            using System;
    
    //            class Program {
    //                public void My() {
    //                    var i = 5;
    //                    do {
    //                        Console.WriteLine("hello world");
    //                        i++;
    //                    }
    //while(!(i > 10));
    //                }
    //            }
    

    Now we can compile updated syntax tree using Roslyn API or save syntaxRoot.GetFullText() to text file and pass it to csc.exe.