53

我一直对此感到困惑一段时间,我环顾四周,找不到任何关于这个主题的讨论。

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

写得很像做..while

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

这样做可以将其转换为有效的 csharp:

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

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

4

6 回答 6

59

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

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

于 2012-07-31T21:41:30.667 回答
10

最大的缺失部分正在连接到管道中,否则你不会比.Emit提供的更进一步。不要误会,Roslyn 带来了很多很棒的东西,但是对于我们这些想要实现预处理器和元编程的人来说,现在似乎还没有考虑到这一点。您可以实现“代码建议”或他们所谓的“问题”/“操作”作为扩展,但这基本上是代码的一次性转换,充当建议的内联替换,而不是您实现新语言的方式特征。这是您始终可以使用扩展完成的事情,但 Roslyn 使代码分析/转换变得非常容易: 在此处输入图像描述

从我在 codeplex 论坛上看到 Roslyn 开发人员的评论来看,在管道中提供挂钩并不是最初的目标。他们在 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# 的特性一样对这些特性进行审查,你可以打赌其中一些不会很好地设计/实现.

于 2014-12-22T19:37:23.310 回答
6

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

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

希望能帮助到你。

于 2015-03-30T17:18:39.110 回答
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直接使用更好是值得怀疑的。

于 2012-07-31T21:38:41.040 回答
1

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

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

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

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

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

于 2019-03-05T08:28:04.303 回答
0

不,没有办法实现你所说的。

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

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

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

并像使用它一样

do {
    //Things happen here
} while (Until(i, 15))
于 2012-07-31T21:27:40.760 回答