有没有办法在C#中实现自定义语言功能?

时间:2012-07-31 21:24:15

标签: c# syntactic-sugar language-construct

我有一段时间一直困惑于此,我看了一下,无法找到关于这个主题的任何讨论。

让我们假设我想实现一个简单的例子,比如一个新的循环结构:do..until

写得非常类似于do..while

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

这可以通过这样做转换为有效的csharp:

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

这显然是一个简单的例子,但有没有办法添加这种性质的东西?理想情况下,作为Visual Studio扩展,以启用语法突出显示等。

6 个答案:

答案 0 :(得分:48)

Microsoft建议使用Rolsyn API作为使用公共API的C#编译器的实现。它包含每个编译器管道阶段的各个API:语法分析,符号创建,绑定,MSIL发射。您可以提供自己的语法分析器实现或扩展现有语法分析器,以获得您想要的任何功能的C#编译器。

Roslyn CTP

让我们使用Roslyn扩展C#语言!在我的例子中,我正在用相应的do-while替换do-until语句:

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));
//                }
//            }

现在我们可以使用Roslyn API编译更新的语法树,或者将syntaxRoot.GetFullText()保存到文本文件并将其传递给csc.exe。

答案 1 :(得分:7)

最大的缺失是插入管道,否则你不会比.Emit提供的更远。不要误解,Roslyn带来了很多很棒的东西,但是对于我们这些想要实现预处理器和元编程的人来说,现在似乎并没有出现问题。您可以将“代码建议”或他们称之为“问题”/“操作”的内容实现为扩展,但这基本上是代码的一次性转换,充当建议的内联替换并且你将实现一种新的语言功能。这是你可以随时使用扩展的东西,但Roslyn使代码分析/转换变得非常容易: enter image description here

从我读过Roslyn开发人员在codeplex论坛上的评论开始,提供钩子进入管道并不是最初的目标。他们在C#6预览版中提供的所有新C#语言功能都涉及修改Roslyn本身。所以你基本上需要分叉Roslyn。他们有关于如何构建Roslyn并使用Visual Studio进行测试的文档。这将是一个繁琐的方式来分叉Roslyn并让Visual Studio使用它。我说严厉,因为现在任何想要使用你的新语言功能的人都必须用你的默认编译器替换它。你可以看到这会开始变得混乱。

Building Roslyn and replacing Visual Studio 2015 Preview's compiler with your own build

另一种方法是构建一个充当Roslyn代理的编译器。 VS可以利用构建编译器的标准API。尽管如此,这不是一项微不足道的任务。您已经阅读了代码文件,调用Roslyn API来转换语法树并发出结果。

使用代理方法的另一个挑战是如何使用智能感知来实现您实现的任何新语言功能。您可能必须拥有C#的“新”变体,使用不同的文件扩展名,并实现Visual Studio所需的所有API以实现智能感知。

最后,考虑C#生态系统,以及可扩展编译器的含义。让我们说Roslyn确实支持这些钩子,它就像提供Nuget包或VS扩展来支持新的语言功能一样简单。所有使用新Do-Until功能的C#本质上都是无效的C#,如果不使用自定义扩展,则无法编译。如果你走得太远,有足够的人实现新功能,很快就会发现不兼容的语言功能。也许有人实现了预处理器宏语法,但它不能与其他人的新语法一起使用,因为他们碰巧使用类似的语法来描述宏的开头。如果你利用很多开源项目并发现自己深入研究他们的代码,你会遇到很多奇怪的语法,需要你跟踪和研究项目正在利用的特定语言扩展。 可能疯狂。我并不是说听起来像一个反对者,因为我对语言功能有很多想法,我对此非常感兴趣,但是应该考虑这个问题的含义,以及它的可维护性。想象一下,如果你被聘请到某个地方工作并且他们已经实现了你必须学习的各种新语法,并且没有那些功能经过C#功能的同样审查,你可以打赌其中一些不会很好地设计/实现

答案 2 :(得分:4)

你可以检查www.metaprogramming.ninja(我是开发人员),它提供了一种简单的方法来完成语言扩展(我提供构造函数,属性,甚至js风格的函数的例子)以及基于完整的语法的DSL。

该项目也是开源的。您可以在github找到文档,示例等。

希望它有所帮助。

答案 3 :(得分:1)

您无法在C#中创建自己的语法抽象,因此您可以做的最好的事情是创建自己的高阶函数。您可以创建Action扩展方法:

public static void DoUntil(this Action act, Func<bool> condition)
{
    do
    {
        act();
    } while (!condition());
}

您可以将其用作:

int i = 1;
new Action(() => { Console.WriteLine(i); i++; }).DoUntil(() => i == 15);

虽然这是否比直接使用do..while更可取是值得怀疑的。

答案 4 :(得分:0)

没有办法实现你所说的。

因为您要问的是定义新的语言构造,所以新的词法分析,语言分析器,语义分析器,生成的IL的编译和优化。

在这种情况下,可以做的是使用某些宏/函数。

public bool Until(int val, int check)
{
   return !(val == check);
}

并像

一样使用它
do {
    //Things happen here
} while (Until(i, 15))

答案 5 :(得分:0)

我发现扩展C#语言最简单的方法是使用T4文本处理器预处理我的源代码。 T4脚本将读取我的C#,然后调用基于Roslyn的解析器,该解析器将使用自定义生成的代码生成新的源代码。

在构建期间,我所有的T4脚本都将被执行,从而有效地用作扩展的预处理器。

在您的情况下,可以按以下方式输入不兼容的C#代码:

#if ExtendedCSharp
     do 
#endif
     {
                    Console.WriteLine("hello world");
                    i++;
     }
#if ExtendedCSharp
                until (i > 10);
#endif

这将允许在程序开发过程中使用语法检查其余(符合C#的)代码。