Existe uma maneira de implementar resources de idioma personalizado em c #?

Eu estive intrigado sobre isso por um tempo e olhei em volta um pouco, incapaz de encontrar qualquer discussão sobre o assunto.

Vamos supor que eu queria implementar um exemplo trivial, como uma nova construção de looping: do..until

Escrito de forma muito semelhante ao do while

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

Isso pode ser transformado em csharp válido ao fazer isso:

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

Este é obviamente um exemplo simples, mas existe alguma maneira de adicionar algo dessa natureza? Idealmente como uma extensão do Visual Studio para ativar o realce de syntax, etc.

A Microsoft propõe a API Rolsyn como uma implementação do compilador C # com API pública. Ele contém APIs individuais para cada um dos estágios do pipeline do compilador: análise de syntax, criação de símbolo, binding, emissão de MSIL. Você pode fornecer sua própria implementação do analisador de syntax ou estender o existente para obter o compilador C # com todos os resources desejados.

Roslyn CTP

Vamos estender a linguagem C # usando o Roslyn! No meu exemplo, estou substituindo a instrução do-until por correspondente 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(); //Looking for do-until statements in all descendant nodes foreach (var doStatement in syntaxRoot.DescendantNodes().OfType()) { //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().FirstOrDefault((_node => { return _node.Identifier.ValueText == "until"; })); //Condition is treated as an argument list var conditionNode = doStatement.Condition.ChildNodes().OfType().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)); // } // } 

Agora podemos compilar a tree de syntax atualizada usando a API do Roslyn ou salvar syntaxRoot.GetFullText () no arquivo de texto e passá-lo para csc.exe.

A grande peça que falta está se conectando ao encanamento, caso contrário, você não está muito mais .Emit que o que .Emit oferecido. Não entenda mal, Roslyn traz muitas coisas boas, mas para aqueles de nós que querem implementar pré-processadores e metaprogramação, parece que por enquanto isso não estava na pauta. Você pode implementar “sugestões de código” ou o que eles chamam de “problemas” / “ações” como uma extensão, mas isso é basicamente uma transformação de código que atua como uma substituição inline sugerida e não é a maneira como você implementaria um novo idioma característica. Isso é algo que você sempre pode fazer com extensões, mas Roslyn torna a análise / transformação de código tremendamente mais fácil: insira a descrição da imagem aqui

Pelo que eu li de comentários de desenvolvedores Roslyn nos fóruns codeplex, fornecer ganchos no pipeline não foi um objective inicial. Todos os novos resources de linguagem C # que eles forneceram na pré-visualização do C # 6 envolveram a modificação da própria Roslyn. Então você essencialmente precisa forçar Roslyn. Eles têm documentação sobre como construir o Roslyn e testá-lo com o Visual Studio. Essa seria uma maneira pesada de assar Roslyn e fazer com que o Visual Studio a usasse. Digo com mão pesada porque agora qualquer um que queira usar seus novos resources de idioma deve replace o compilador padrão pelo seu. Você poderia ver onde isso começaria a ficar confuso.

Construindo Roslyn e substituindo o compilador do Visual Studio 2015 Preview com sua própria compilation

Outra abordagem seria criar um compilador que funcionasse como um proxy para Roslyn. Existem APIs padrão para construir compiladores que o VS pode aproveitar. Não é uma tarefa trivial. Você leu nos arquivos de código, invocou as APIs Roslyn para transformar as trees de syntax e emitir os resultados.

O outro desafio com a abordagem de proxy será obter o intellisense para tocar bem com os novos resources de idioma que você implementar. Você provavelmente teria que ter sua “nova” variante de C #, usar uma extensão de arquivo diferente e implementar todas as APIs que o Visual Studio requer para o intellisense funcionar.

Por fim, considere o ecossistema C # e o que um compilador extensível significaria. Digamos que Roslyn tenha suportado esses ganchos, e foi tão fácil quanto fornecer um pacote Nuget ou uma extensão VS para suportar um novo recurso de idioma. Todo o seu C # aproveitando o novo recurso Do-Until é essencialmente C # inválido, e não irá compilar sem o uso de sua extensão personalizada. Se você for longe o bastante nessa estrada com pessoas suficientes implementando novos resources, muito rapidamente você encontrará resources de linguagem incompatíveis. Talvez alguém implemente uma syntax de macro de pré-processador, mas ela não pode ser usada ao lado da nova syntax de outra pessoa porque ela usou uma syntax semelhante para delinear o início da macro. Se você alavancar muitos projetos de código aberto e descobrir o código deles, você encontrará uma estranha syntax que exigiria que você acompanhasse e pesquisasse as extensões de linguagem específicas que o projeto está alavancando. Pode ser loucura. Não pretendo soar como um opositor, pois tenho muitas ideias para características de linguagem e estou muito interessado nisso, mas deve-se considerar as implicações disso e como isso seria sustentável. Imagine se você fosse contratado para trabalhar em algum lugar e eles tivessem implementado todos os tipos de novas syntaxs que você tinha que aprender, e sem esses resources terem sido avaliados da mesma forma que os resources do C #, você pode apostar que alguns deles não seriam bem projetados .

Você pode verificar http://www.metaprogramming.ninja (eu sou o desenvolvedor), ele fornece uma maneira fácil de realizar extensões de linguagem (eu forneço exemplos para construtores, propriedades, até mesmo funções de estilo js) bem como DSLs baseadas em gramática.

O projeto é de código aberto também. Você pode encontrar documentações, exemplos, etc no github .

Espero que ajude.

Você não pode criar suas próprias abstrações sintáticas em C #, então o melhor que você pode fazer é criar sua própria function de ordem superior. Você poderia criar um método de extensão Action :

 public static void DoUntil(this Action act, Func condition) { do { act(); } while (!condition()); } 

Qual você pode usar como:

 int i = 1; new Action(() => { Console.WriteLine(i); i++; }).DoUntil(() => i == 15); 

embora seja questionável se é preferível usar um do..while diretamente.

Não há como conseguir o que você está falando.

Porque o que você está perguntando é sobre a definição de um novo constructo de linguagem, assim, uma nova análise lexical, um analisador de linguagem, um analisador semântico, uma compilation e uma otimização do IL gerado.

O que você pode fazer nesses casos é o uso de algumas macros / funções.

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

e usá-lo como

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