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

The*_*eze 43 c# language-construct syntactic-sugar

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

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

写得非常类似于do..while

do {
    //Things happen here
} until (i == 15)
Run Code Online (Sandbox Code Playgroud)

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

do {
    //Things happen here
} while (!(i == 15))
Run Code Online (Sandbox Code Playgroud)

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

Ram*_*ich 50

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

罗斯林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));
//                }
//            }
Run Code Online (Sandbox Code Playgroud)

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

  • 有没有办法将其插入到Visual Studio中,以便在自定义语言功能上获得正确的Intellisence和语法突出显示? (7认同)
  • 这个例子似乎过于限制解析器可以理解的东西。将“until”(被解析为函数调用)转换为其他内容更像是一种黑客行为。如何更改语法文件以便添加新的语言结构? (2认同)

Aar*_*nLS 9

最大的缺失是挂在管道上,否则你没有比.Emit提供的更远.不要误解,Roslyn带来了很多很棒的东西,但是对于我们这些想要实现预处理器和元编程的人来说,现在似乎还没有出现.您可以将"代码建议"或他们称之为"问题"/"操作"的内容实现为扩展,但这基本上是代码的一次性转换,充当建议的内联替换,而不是您实现新语言的方式特征.这是你可以随时使用扩展的东西,但Roslyn使代码分析/转换变得非常容易: 在此输入图像描述

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

构建Roslyn并使用您自己的构建替换Visual Studio 2015 Preview的编译器

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

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

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


Emi*_*tos 5

您可以访问www.metaprogramming.ninja(我是开发人员),它提供了一种轻松的方法来完成语言扩展(我提供了有关构造函数,属性甚至js样式功能的示例)以及基于语法的成熟DSL。

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

希望能帮助到你。