web-dev-qa-db-ja.com

C#でカスタム言語機能を実装する方法はありますか?

私はしばらくこれについて困惑していて、少し見回してみましたが、この主題についての議論を見つけることができませんでした。

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

と非常によく似て書かれています。

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

これを行うことで、有効なcsharpに変換できます。

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

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

46
Thebigcheeze

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

Roslyn 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に渡すことができます。

53
Raman Zhylich

大きな欠けている部分はパイプラインに引っ掛かっています、さもなければあなたは何よりもはるかに進んでいません.Emit提供。誤解しないでください、Roslynはたくさんの素晴らしいものをもたらしますが、プリプロセッサとメタプログラミングを実装したい私たちにとって、今のところそれはプレート上になかったようです。 「コード提案」または「問題」/「アクション」と呼ばれるものを拡張機能として実装できますが、これは基本的に、提案されたインライン置換として機能するコードの1回限りの変換であり、 not新しい言語機能を実装する方法。これは、拡張機能を使用して常に実行できることですが、Roslynを使用すると、コードの分析/変換が非常に簡単になります。 enter image description here

コードプレックスフォーラムでRoslyn開発者からのコメントを読んだところ、パイプラインへのフックを提供することは最初の目標ではありませんでした。 C#6プレビューで提供されたすべての新しいC#言語機能には、Roslyn自体の変更が含まれていました。したがって、基本的にRoslynをフォークする必要があります。 Roslynをビルドし、VisualStudioでテストする方法に関するドキュメントがあります。これは、Roslynをフォークして、VisualStudioに使用させるための手間のかかる方法です。新しい言語機能を使用したい人は、デフォルトのコンパイラを自分のものに置き換える必要があるので、私は手間がかかると言います。これがどこで乱雑になり始めるかを見ることができます。

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

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

プロキシアプローチのもう1つの課題は、実装する新しい言語機能でうまく機能するようにインテリセンスを取得することです。おそらく、C#の「新しい」バリアントを用意し、別のファイル拡張子を使用し、VisualStudioがインテリセンスを機能させるために必要なすべてのAPIを実装する必要があります。

最後に、C#エコシステムと、拡張可能なコンパイラの意味について考えてみましょう。 Roslynがこれらのフックをサポートしていて、新しい言語機能をサポートするためにNugetパッケージまたはVS拡張機能を提供するのと同じくらい簡単だったとしましょう。新しいDo-Until機能を利用するすべてのC#は本質的に無効なC#であり、カスタム拡張機能を使用しないとコンパイルされません。十分な数の人が新しい機能を実装してこの道を十分に進んだ場合、互換性のない言語機能がすぐに見つかります。誰かがプリプロセッサマクロ構文を実装しているかもしれませんが、マクロの始まりを描写するためにたまたま同様の構文を使用しているため、他の誰かの新しい構文と一緒に使用することはできません。たくさんのオープンソースプロジェクトを活用していて、それらのコードを掘り下げていることに気付いた場合、プロジェクトが活用している特定の言語拡張機能を追跡して調査する必要がある奇妙な構文に遭遇するでしょう。それは狂気である可能性があります。私は言語機能について多くのアイデアを持っており、これに非常に興味を持っているので、否定論者のように聞こえるつもりはありませんが、これの意味と、それがどれほど維持可能であるかを考慮する必要があります。あなたがどこかで働くために雇われ、彼らがあなたが学ばなければならないあらゆる種類の新しい構文を実装し、それらの機能がC#の機能と同じように精査されていなければ、それらのいくつかがうまく設計/実装されていないことは間違いありません。 。

10
AaronLS

あなたはチェックすることができます www.metaprogramming.ninja (私は開発者です)、それは言語拡張(コンストラクター、プロパティ、さらにはjsスタイルの関数の例を提供します)を達成する簡単な方法を提供します。 -吹き飛ばされた文法ベースのDSL。

プロジェクトもオープンソースです。ドキュメントや例などは github にあります。

それが役に立てば幸い。

6
Emilio Santos

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
Lee

C#言語を拡張する最も簡単な方法は、T4テキストプロセッサを使用してソースを前処理することです。 T4スクリプトは私のC#を読み取り、Roslynベースのパーサーを呼び出します。これにより、カスタム生成されたコードで新しいソースが生成されます。

ビルド時に、すべてのT4スクリプトが実行されるため、拡張プリプロセッサとして効果的に機能します。

あなたの場合、非準拠のC#コードは次のように入力できます。

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

これにより、プログラムの開発中に残りの(C#準拠の)コードを構文チェックできます。

1

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

あなたが求めているのは、新しい言語構造を定義することです。つまり、新しい字句解析、言語パーサー、セマンティックアナライザー、生成されたILのコンパイルと最適化です。

このような場合にcanすることは、いくつかのマクロ/関数の使用です。

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

のように使用します

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