Есть ли способ реализовать пользовательские функции языка в С#?

Я немного задумывался об этом, и я немного огляделся, не смог найти обсуждения по этому вопросу.

Предположим, что я хотел реализовать тривиальный пример, например новую конструкцию цикла: do..until

Написано очень похоже на do..while

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

Это можно было бы преобразовать в действительный csharp, сделав так:

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

Это, очевидно, простой пример, но есть ли способ добавить что-то подобное? В идеале, как расширение Visual Studio для включения подсветки синтаксиса и т.д.

Ответы

Ответ 1

Microsoft предлагает Rolsyn API как реализацию компилятора С# с открытым API. Он содержит отдельные API для каждой стадии конвейера компилятора: синтаксический анализ, создание символа, привязка, эмиссия MSIL. Вы можете предоставить собственную реализацию синтаксического синтаксического анализатора или расширить существующий, чтобы получить компилятор С# с любыми функциями, которые вы хотели бы.

Roslyn CTP

Позвольте расширить язык С#, используя Roslyn! В моем примере я заменяю do-until statement w/соответствующий 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 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));
//                }
//            }

Теперь мы можем скомпилировать обновленное дерево синтаксиса с использованием API Roslyn или сохранить файл syntaxRoot.GetFullText() в текстовый файл и передать его в файл csc.exe.

Ответ 2

Большой недостающий элемент подключается к конвейеру, иначе вы не намного дальше, чем .Emit. Не поймите неправильно, Roslyn приносит много замечательных вещей, но для тех из нас, кто хочет реализовать препроцессоры и мета-программирование, кажется, что пока это не было на тарелке. Вы можете реализовать "предложения кода" или то, что они называют "проблемами" / "действия" в качестве расширения, но это в основном однократное преобразование кода, которое действует как предлагаемая встроенная замена и не. вы бы реализовали новую языковую функцию. Это то, что вы всегда можете делать с расширениями, но Roslyn значительно упрощает анализ/преобразование кода: enter image description here

Из того, что я читал о комментариях разработчиков Roslyn на форумах codeplex, предоставление крючков в конвейер не было начальной целью. Все новые функции языка С#, которые они предоставили в превью С# 6, включали в себя модификацию самого Roslyn. Поэтому вам по существу нужно разветкить Рослин. У них есть документация о том, как построить Roslyn и протестировать его с помощью Visual Studio. Это было бы тяжелым способом разблокировать Roslyn и использовать Visual Studio. Я говорю тяжело, потому что теперь любой, кто хочет использовать ваши новые функции языка, должен заменить ваш компилятор по умолчанию. Вы могли видеть, где это начнет запутываться.

Построение Roslyn и замена компилятора Preview Studio 2015 с вашей собственной сборкой

Другим подходом было бы создание компилятора, который действует как прокси-сервер для Roslyn. Существуют стандартные API для построения компиляторов, которые VS может использовать. Однако это не тривиальная задача. Вы прочитали бы в файлах кода, призовите API Roslyn, чтобы преобразовать деревья синтаксиса и испустить результаты.

Другая проблема с прокси-подходом будет заключаться в том, чтобы заставить intellisense хорошо играть с любыми новыми языковыми функциями, которые вы реализуете. Вероятно, вам придется иметь свой "новый" вариант С#, использовать другое расширение файла и реализовать все API, которые Visual Studio требует для работы intellisense.

Наконец, рассмотрим экосистему С# и то, что означало бы расширяемый компилятор. Скажем, Roslyn действительно поддерживал эти перехватчики, и было так же просто, как предоставить пакет Nuget или расширение VS для поддержки новой языковой функции. Все ваши С#, использующие новую функцию Do-Until, по сути являются недействительными С# и не будут компилироваться без использования вашего пользовательского расширения. Если вы пройдете достаточно далеко по этой дороге с достаточным количеством людей, реализующих новые функции, очень быстро вы найдете несовместимые языковые возможности. Возможно, кто-то реализует синтаксис макроса препроцессора, но его нельзя использовать рядом с другим синтаксисом, потому что они использовали аналогичный синтаксис для определения начала макроса. Если вы используете много проектов с открытым исходным кодом и обнаружите, что вникаете в их код, вы столкнетесь с большим количеством странного синтаксиса, который потребует от вас побочного отслеживания и изучения конкретных языковых расширений, которые использует этот проект. Это может быть безумие. Я не хочу звучать как наяйер, поскольку у меня много идей по языковым особенностям, и я очень заинтересован в этом, но нужно учитывать последствия этого и насколько это возможно. Представьте себе, что если вы наняли кого-нибудь на работу, и они внедрили все новые синтаксисы, которые вам нужно было изучить, и без этих функций, которые были проверены так же, как у С# -функций, вы можете поспорить, что некоторые из них будут плохо разработаны и реализованы.

Ответ 3

Вы можете проверить www.metaprogramming.ninja (я разработчик), он обеспечивает простой способ выполнения языковых расширений (я предоставляю примеры для конструкторов, свойства, даже функции js-стиля), а также полноразмерные DSL-грамматики.

Проект также является открытым исходным кодом. Вы можете найти документацию, примеры и т.д. В github.

Надеюсь, что это поможет.

Ответ 4

Нет никакого способа добиться того, о чем вы говорите.

Причина, о которой вы просите, - это определение новой языковой конструкции, поэтому новый лексический анализ, парсер языка, семантический анализатор, компиляция и оптимизация сгенерированного IL.

В таких случаях вы можете использовать некоторые макросы/функции.

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

и используйте его как

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

Ответ 5

Вы не можете создавать свои собственные синтаксические абстракции в С#, поэтому лучше всего вы можете создать свою собственную функцию более высокого порядка. Вы можете создать метод расширения 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.