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

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

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

Написано очень похоже на делать .. пока

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

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

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

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


person Thebigcheeze    schedule 31.07.2012    source источник
comment
Несколько более простым решением было бы не добавлять их в сам C #, а создать DSL или подъязык, который можно было бы встроить и предварительно обработать / предварительно скомпилировать в C # (или MSIL) перед запуском компилятора C #. Больше шагов, но тоже немного мощнее.   -  person ssube    schedule 01.08.2012
comment
Это в значительной степени именно то, что я пытаюсь сделать. Есть ли у вас какие-либо ссылки или книги, на которые вы могли бы указать мне, о том, как подключиться к конвейеру сборки, как вы предлагаете?   -  person Thebigcheeze    schedule 01.08.2012
comment
Возможно, вы захотите посмотреть на другие языки, которые больше подходят для определения ваших собственных управляющих конструкций. Т.е. Семейство LISP / Scheme известно этим и имеет некоторые реализации .Net (stackoverflow.com/questions/110433/).   -  person Alexei Levenkov    schedule 01.08.2012
comment
Nemerle - это язык, производный от C #, в котором есть конструкции, позволяющие вам определять в нем свой собственный синтаксис. Однако это не супернабор C #, поскольку он не может скомпилировать существующий код C # без изменений.   -  person AaronLS    schedule 13.01.2015


Ответы (6)


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

Roslyn CTP

Давайте расширим язык C # с помощью Roslyn! В моем примере я заменяю оператор 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.

person Raman Zhylich    schedule 31.07.2012
comment
Кажется, это то направление, в котором я хотел бы двигаться. Большая часть информации, которую я смог найти о Roslyn, касается таких вещей, как настраиваемый рефакторинг и проблемы с настраиваемым кодом. Есть ли у вас какие-либо источники, которые показывают, как привязать к конкретным этапам конвейера? Спасибо :) - person Thebigcheeze; 01.08.2012
comment
@Thebigcheeze На Roslyn пока мало чего интересного. Я сам реализовал ваш пример (см. Обновленный ответ). Кроме того, я нашел хорошую статью, объясняющую принципы расширения языка C #: mindscapehq.com/blog/index.php/2011/10/20/in-bed-with-roslyn - person Raman Zhylich; 01.08.2012
comment
Есть ли способ подключить это к Visual Studio, чтобы получить правильную интеллект и подсветку синтаксиса для пользовательских языковых функций? - person Bradley Uffner; 30.03.2015
comment
@RamanZhylich, у вас есть ключ к решению этого stackoverflow .com / questions / 38786359 /? - person Matthias Burger; 05.08.2016
comment
Этот пример кажется слишком ограниченным для понимания синтаксического анализатора. Преобразование until (который анализируется как вызов функции) во что-то еще больше похоже на взлом. Как изменить файл грамматики, чтобы мы могли добавлять новые языковые конструкции? - person fernacolo; 01.07.2019

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

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

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

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

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

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

person AaronLS    schedule 22.12.2014
comment
Исторически это хороший ответ, но я хочу прояснить, что в последние годы Roslyn позволяет гораздо больше, чем просто это. Проблема в том, что программирование компилятора чрезвычайно сложно и легко испортить, а не в том, что Roslyn не позволяет вам включать новые языковые функции. - person leviathanbadger; 09.08.2016
comment
Согласованный. Конечно, я не охватил весь спектр вещей, которые вы можете делать с Roslyn. Однако дело в том, что до сих пор нет точки интеграции для изменения компилятора C #. Единственный способ сделать это - перекомпилировать его и произвести полную замену, которая на самом деле предназначена только для тестирования. Если бы я реализовал языковую функцию A, а вы реализовали языковую функцию B, нам пришлось бы объединить наш исходный код компилятора, чтобы использовать обе функции: github.com/dotnet/roslyn/blob/master/docs/contributing/ - person AaronLS; 09.08.2016
comment
Я начинаю изучать возможность использования Roslyn для добавления некоторых настроек в язык C #. Одна вещь, в которой я не уверен, заключается в том, что в старой ссылке CodePlex, которую вы разместили, похоже, подразумевается, что модифицированный Roslyn может использоваться для выдачи модифицированного кода для .Net Framework. В новой ссылке на GitHub, которую вы разместили, они говорят только о .Net Core, .Net Core, .Net Core, как будто они забыли о .Net Framework. Как вы думаете, возможно ли изменить выпущенный код для .Net Framework? Или я все неправильно понял? - person RenniePet; 26.01.2018
comment
Насколько я понимаю, руководство по замене компилятора Visual Studio заменит ваш компилятор таким образом, чтобы его можно было использовать для компиляции проектов, ориентированных на .NET Framework или Core. Обратите внимание: если вы хотите генерировать код во время выполнения с помощью Roslyn, вам не нужно заменять компилятор VS. Вы используете Roslyn как компилятор как услугу для вывода. Замена компилятора VS - это то, что вы сделали бы, чтобы поэкспериментировать с добавлением функций в компилятор VS, что обычно не называется emit. - person AaronLS; 27.01.2018

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

У проекта тоже открытый исходный код. Вы можете найти документацию, примеры и т. Д. На github.

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

person Emilio Santos    schedule 30.03.2015
comment
Вы изменили название проекта? - person RenniePet; 26.01.2018
comment
первая ссылка не работает. - person OfirD; 26.08.2020

Вы не можете создавать свои собственные синтаксические абстракции на 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.

person Lee    schedule 31.07.2012

Я обнаружил, что самый простой способ расширить язык C # - это использовать текстовый процессор T4 для предварительной обработки исходного кода. Сценарий T4 прочитает мой C #, а затем вызовет синтаксический анализатор на основе Roslyn, который сгенерирует новый источник с пользовательским сгенерированным кодом.

Во время сборки все мои сценарии T4 будут выполняться, таким образом, эффективно работая как расширенный препроцессор.

В вашем случае несовместимый код C # можно ввести следующим образом:

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

Это позволит проверять синтаксис остальной части вашего (совместимого с C #) кода во время разработки вашей программы.

person Wolfgang Grinfeld    schedule 05.03.2019

Нет, невозможно достичь того, о чем вы говорите.

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

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

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

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

do {
    //Things happen here
} while (Until(i, 15))
person Tigran    schedule 31.07.2012
comment
Как вы намекнули в своем первом предложении, именно это я и пытаюсь сделать. Однако вместо того, чтобы писать новый парсер, лексер, компилятор, мне интересно, есть ли способ расширить существующие. - person Thebigcheeze; 01.08.2012
comment
Что ж, расширите его, используя вашу функцию, которая будет скомпилирована в IL без каких-либо изменений. - person Tigran; 01.08.2012
comment
Да, я понимаю это, и это простой пример, иллюстрирующий эту мысль, но еще один пример: у Csharp есть методы расширения. Это чрезвычайно простой синтаксический сахар. Могу ли я сам реализовать методы расширения? - person Thebigcheeze; 01.08.2012
comment
@Thebigcheeze: нет, нет способа сделать это, кроме как с помощью настройки лексера, парсера, компилятора ... - person Tigran; 01.08.2012