53

私はこれについてしばらく困惑しており、少し調べてみましたが、この件に関する議論は見つかりませんでした.

新しいループ構造のような簡単な例を実装したいとしましょう: do..until

do..while と非常によく似た書き方

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

これを有効な csharp に変換するには、次のようにします。

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

これは明らかに単純な例ですが、この性質のものを追加する方法はありますか? 構文の強調表示などを有効にするための Visual Studio 拡張機能として理想的です。

4

6 に答える 6

59

Microsoft は、パブリック API を使用した C# コンパイラの実装として Rolsyn API を提案しています。これには、構文解析、シンボル作成、バインド、MSIL 発行などのコンパイラ パイプライン ステージごとに個別の API が含まれています。構文パーサーの独自の実装を提供するか、既存のものを拡張して、必要な機能を備えた C# コンパイラを取得できます。

ロズリン CTP

Roslynを使ってC#言語を拡張しよう!私の例では、do-until ステートメントを対応する do-while に置き換えています。

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 は多くの優れた機能をもたらしますが、プリプロセッサとメタ プログラミングを実装したいと考えている私たちにとっては、現時点では予定されていなかったようです。「コード提案」または「問題」/「アクション」と呼ばれるものを拡張機能として実装できますが、これは基本的に提案されたインライン置換として機能するコードの 1 回限りの変換であり、新しい言語を実装する方法ではありません。特徴。これは、拡張機能を使用して常に実行できることですが、Roslyn を使用すると、コードの分析/変換が非常に簡単になります。 ここに画像の説明を入力

Codeplex フォーラムで Roslyn 開発者から寄せられたコメントを読んだ限りでは、パイプラインにフックを提供することは当初の目標ではありませんでした。彼らが C# 6 プレビューで提供したすべての新しい C# 言語機能には、Roslyn 自体の変更が含まれていました。したがって、本質的に Roslyn をフォークする必要があります。Roslyn をビルドして Visual Studio でテストする方法に関するドキュメントがあります。これは、Roslyn を fork して Visual Studio に使用させるには、手荒な方法です。あなたの新しい言語機能を使用したい人は誰でもデフォルトのコンパイラをあなたのものに置き換えなければならないので、私は強引だと言います. これが乱雑になり始める場所を見ることができました。

Roslyn をビルドし、Visual Studio 2015 Preview のコンパイラを独自のビルドに置き換える

もう 1 つの方法は、Roslyn のプロキシとして機能するコンパイラを構築することです。VS が活用できるコンパイラを構築するための標準 API があります。しかし、それは簡単な作業ではありません。コード ファイルを読み取り、Roslyn API を呼び出して構文ツリーを変換し、結果を出力します。

プロキシ アプローチのもう 1 つの課題は、実装する新しい言語機能を IntelliSense で適切に動作させることです。おそらく、C# の "新しい" バリアントを用意し、別のファイル拡張子を使用し、IntelliSense が機能するために 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これが aを直接使用するよりも好ましいかどうかは疑問ですが。

于 2012-07-31T21:38:41.040 に答える
0

いいえ、あなたが話していることを達成する方法はありません。

あなたが求めているのは、新しい言語構造を定義することなので、新しい字句解析、言語パーサー、セマンティック アナライザー、コンパイル、および generated の最適化です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 に答える