Есть ли способ реализовать пользовательские функции языка в С#?
Я немного задумывался об этом, и я немного огляделся, не смог найти обсуждения по этому вопросу.
Предположим, что я хотел реализовать тривиальный пример, например новую конструкцию цикла: 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 значительно упрощает анализ/преобразование кода:
Из того, что я читал о комментариях разработчиков 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
.