簡體   English   中英

有沒有辦法在C#中實現自定義語言功能?

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

我已經對此感到困惑了一段時間,並且四處張望,無法找到有關該主題的任何討論。

假設我想實現一個簡單的示例,例如一個新的循環結構:do..until

寫得非常類似。

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

這樣做可以將其轉換為有效的csharp:

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

這顯然是一個簡單的示例,但是有什么方法可以添加這種性質的東西嗎? 理想情況是作為Visual Studio擴展來啟用語法突出顯示等。

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編譯更新的語法樹,或將語法Root.GetFullText()保存到文本文件並將其傳遞給csc.exe。

遺漏的最大部分正在進入管道,否則您與.Emit提供的內容.Emit 別誤會,羅斯林帶來了很多偉大的東西,但是對於我們這些想要實現預處理器和元編程的人來說,現在看來並沒有出現。 您可以實現“代碼建議”或它們所謂的“問題” /“動作”作為擴展,但這基本上是對代碼的一次性轉換,可以作為建議的內聯替換,而不是實現新語言的方式特征。 這是您可以始終使用擴展進行的操作,但是Roslyn使代碼分析/轉換變得非常容易: 在此處輸入圖片說明

從我在Codeplex論壇上從Roslyn開發人員的評論中了解到,提供連接到管道並不是最初的目標。 他們在C#6預覽版中提供的所有新C#語言功能都涉及修改Roslyn本身。 因此,您基本上需要分叉羅斯林。 他們有關於如何構建Roslyn以及如何使用Visual Studio對其進行測試的文檔。 這將是派生Roslyn並讓Visual Studio使用它的繁重方法。 我之所以說是笨拙的,是因為現在任何想要使用您的新語言功能的人都必須用您的默認語言替換默認的編譯器。 您可以看到哪里開始變得混亂。

生成Roslyn並用自己的生成替換Visual Studio 2015 Preview的編譯器

另一種方法是構建充當Roslyn代理的編譯器。 VS可以利用一些標准的API來構建編譯器。 但是,這並不是一件容易的事。 您已經閱讀了代碼文件,調用Roslyn API來轉換語法樹並發出結果。

代理方法的另一個挑戰將是使智能感知與您實現的任何新語言功能完美配合。 您可能必須擁有C#的“新”變體,使用不同的文件擴展名,並實現Visual Studio才能運行intellisense所需的所有API。

最后,考慮一下C#生態系統以及可擴展的編譯器的含義。 假設Roslyn確實支持這些鈎子,就像提供Nuget包或VS擴展來支持新的語言功能一樣簡單。 利用新的Do-Until功能的所有C#本質上都是無效的C#,並且在不使用自定義擴展名的情況下將無法編譯。 如果您走了足夠遠的路,並且有足夠多的人實現了新功能,那么很快您就會發現不兼容的語言功能。 也許有人實現了預處理器宏語法,但不能與其他人的新語法一起使用,因為他們碰巧使用類似的語法來描述宏的開頭。 如果您利用大量開放源代碼項目並發現自己正在研究其代碼,則會遇到很多奇怪的語法,這些語法要求您單獨跟蹤和研究該項目所利用的特定語言擴展。 可能是瘋狂的。 我並不是要聽起來像個反對者,因為我對語言功能有很多想法並且對此很感興趣,但是我應該考慮一下它的含義以及它的可維護性。 想象一下,如果您被雇用在某個地方工作,並且他們已經實現了您必須學習的各種新語法,並且沒有像對C#的功能那樣對這些功能進行過審查,那么您可以打賭其中的某些功能將無法很好地設計/實現。

您可以訪問www.metaprogramming.ninja (我是開發人員),它提供了一種輕松的方法來完成語言擴展(我提供了有關構造函數,屬性甚至js樣式函數的示例)以及基於語法的全面DSL。

該項目也是開源的。 您可以在github上找到文檔,示例等。

希望能幫助到你。

您無法在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更可取是一個問題。

我發現擴展C#語言最簡單的方法是使用T4文本處理器預處理我的源代碼。 T4腳本將讀取我的C#,然后調用基於Roslyn的解析器,該解析器將使用自定義生成的代碼生成新的源。

在構建期間,我所有的T4腳本都將被執行,從而有效地用作擴展的預處理器。

在您的情況下,可以按以下方式輸入不兼容的C#代碼:

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

這將允許在程序開發期間使用語法檢查其余(符合C#的)代碼。

沒有任何方法可以實現您所談論的內容。

因為您要問的是定義新的語言結構,所以要進行新的詞法分析,語言解析器,語義分析器,生成的IL編譯和優化。

在這種情況下,您可以做的是使用某些宏/函數。

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

並像這樣使用

do {
    //Things happen here
} while (Until(i, 15))

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM