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

问题描述 投票:41回答:6

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

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

写得非常类似于do..while

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

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

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

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

c# syntactic-sugar language-construct
6个回答
49
投票

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。


9
投票

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

从我读过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来转换语法树并发出结果。

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

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


5
投票

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

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

希望能帮助到你。


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更可取是值得怀疑的。


1
投票

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

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

在您的情况下,可以按如下方式输入不符合要求的C#代码:

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

这将允许在程序开发期间检查其余(C#兼容)代码的语法。


0
投票

没有办法实现你所说的。

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

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

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

并使用它

do {
    //Things happen here
} while (Until(i, 15))
© www.soinside.com 2019 - 2024. All rights reserved.