Синтаксический анализ кода C# (как строки) и вставка дополнительных методов
у меня есть приложение c#, над которым я работаю, которое загружает его код удаленно, а затем запускает его (для аргумента вы можете предположить, что приложение безопасно).
код C#, но он отправляется как XML-документ, анализируется как строка, а затем компилируется и выполняется.
теперь, что я хотел бы сделать - и у меня немного больше трудностей, чем я ожидал, - это иметь возможность анализировать весь документ, а перед компиляцией вставлять дополнительные команды после каждой строки исполнение.
например, рассмотрим код:
using System;
using System.Collections.Generic;
using System.Linq;
namespace MyCode
{
static class MyProg
{
static void Run()
{
int i = 0;
i++;
Log(i);
}
}
}
что я хотел бы, после разбора что-то больше похоже:
using System;
using System.Collections.Generic;
using System.Linq;
namespace MyCode
{
static class MyProg
{
static void Run()
{
int i = 0;
MyAdditionalMethod();
i++;
MyAdditionalMethod();
Log(i);
MyAdditionalMethod();
}
}
}
имейте в виду очевидные подводные камни - я не могу просто иметь его после каждого двоеточия, потому что это не будет работать в геттере / сеттере, т. е.:
преобразование:
public string MyString { get; set; }
в:
public string MyString { get; MyAdditionalMethod(); set; MyAdditionalMethod(); }
потерпит неудачу. Как и объявления уровня класса, операторы using и т. д. Кроме того, существует ряд случаев, когда Я также мог бы добавить в MyAdditionalMethod () после фигурных скобок - как в делегатах, сразу после операторов if или объявлений методов и т. д.
Итак, что я искал в CodeDOM, и это похоже на то, что это может быть решением, но трудно понять, с чего начать. В противном случае я пытаюсь проанализировать все это и создать дерево, которое я могу проанализировать, хотя это немного сложно, учитывая количество случаев, которые мне нужно рассмотреть.
кто-нибудь знает есть еще какие-то решения?
5 ответов
есть несколько парсеров c#, я бы рекомендовал использовать что-то из Mono или SharpDevelop, поскольку они должны быть актуальными. Я пошел, используя NRefactory от SharpDevelop, если вы скачать источник для SharpDevelop есть демо и некоторые UnitTests, которые являются хорошим вступлением к его использованию.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ICSharpCode.NRefactory;
using System.IO;
using ICSharpCode.NRefactory.Ast;
using ICSharpCode.NRefactory.Visitors;
using ICSharpCode.NRefactory.PrettyPrinter;
namespace Parse
{
class Program
{
static void Main(string[] args)
{
string code = @"using System;
using System.Collections.Generic;
using System.Linq;
namespace MyCode
{
static class MyProg
{
static void Run()
{
int i = 0;
i++;
Log(i);
}
}
}
";
IParser p = ParserFactory.CreateParser(SupportedLanguage.CSharp, new StringReader(code));
p.Parse();
//Output Original
CSharpOutputVisitor output = new CSharpOutputVisitor();
output.VisitCompilationUnit(p.CompilationUnit, null);
Console.Write(output.Text);
//Add custom method calls
AddMethodVisitor v = new AddMethodVisitor();
v.VisitCompilationUnit(p.CompilationUnit, null);
v.AddMethodCalls();
output = new CSharpOutputVisitor();
output.VisitCompilationUnit(p.CompilationUnit, null);
//Output result
Console.Write(output.Text);
Console.ReadLine();
}
}
//The vistor adds method calls after visiting by storing the nodes in a dictionary.
public class AddMethodVisitor : ConvertVisitorBase
{
private IdentifierExpression member = new IdentifierExpression("MyAdditionalMethod");
private Dictionary<INode, INode> expressions = new Dictionary<INode, INode>();
private void AddNode(INode original)
{
expressions.Add(original, new ExpressionStatement(new InvocationExpression(member)));
}
public override object VisitExpressionStatement(ExpressionStatement expressionStatement, object data)
{
AddNode(expressionStatement);
return base.VisitExpressionStatement(expressionStatement, data);
}
public override object VisitLocalVariableDeclaration(LocalVariableDeclaration localVariableDeclaration, object data)
{
AddNode(localVariableDeclaration);
return base.VisitLocalVariableDeclaration(localVariableDeclaration, data);
}
public void AddMethodCalls()
{
foreach (var e in expressions)
{
InsertAfterSibling(e.Key, e.Value);
}
}
}
}
вам нужно будет улучшить посетителя, чтобы обрабатывать больше случаев, но это хорошее начало.
в качестве альтернативы вы можете скомпилировать оригинал и сделайте некоторые манипуляции IL, используя Сесил или попробуйте библиотеку AOP, например PostSharp. Наконец, вы могли бы заглянуть в API профилирования .NET.
вы можете использовать систему преобразования программы "Источник-Источник". Такой инструмент анализирует код, строит и ASTs, позволяет применять преобразования, а затем восстанавливает текст из AST. Что делает систему source-to-source приятной, так это то, что вы можете писать преобразования с точки зрения синтаксиса исходного языка, а не фрактальной детали AST, что делает их намного проще писать и понимать позже.
то, что вы хотите сделать, будет смоделировано довольно простой программой преобразование используя наш инструментарий реинжиниринга программного обеспечения DMS:
rule insert_post_statement_call(s: stmt): stmt -> stmt =
" \s " -> " { \s ; MyAdditionalMethod(); }";
это правило не является подстановкой" текст"; скорее, оно анализируется синтаксическим анализатором, который обрабатывает целевой код, и поэтому на самом деле оно представляет два AST, левую и правую стороны (разделенные синтаксисом" ->". Кавычки не являются строковыми кавычками; они являются кавычками вокруг синтаксиса целевого языка, чтобы отличить его от синтаксиса самого языка правил. Что находится внутри кавычек-это цель язык (например, C#) текст с эскападами типа \s, которые представляют собой целые языковые элементы (в данном случае stmt в соответствии с грамматикой целевого языка (например, C#). Левая сторона говорит: "сопоставьте любое утверждение s", потому что s определяется как "stmt" в грамматике. Правая сторона говорит: "замените оператор блоком, содержащим исходный оператор \S, и новый код, который вы хотите вставить". Все это делается с точки зрения синтаксических деревьев с использованием грамматики в качестве руководства; он не может применять превратить все, что не является заявлением. [Причина переписывания оператора как блока заключается в том, что таким образом правая сторона и valid где операторы действительны, перейдите проверить грамматику.]
на практике вам нужно будет написать правила для обработки других особых случаев, но это в основном писать больше правил. Вам также нужно упаковать парсер / трансформатор / prettyprinter как пакет, который требует некоторого процедурного клея. Это все еще намного проще, чем пытаться напишите код, чтобы надежно карабкаться вверх и вниз по дереву, сопоставляя узлы, а затем разбивая эти узлы, чтобы получить то, что вы хотите. Лучше, когда ваша грамматика (неизменно) должна быть скорректирована, правила перезаписи переделываются в соответствии с пересмотренной грамматикой и все еще работают; независимо от того, какое процедурное дерево вы можете делать, почти наверняка gauranteed сломать.
по мере того как вы пишете все больше и больше преобразований, эта возможность становится все более и более ценной. И когда вы будете успешны с небольшое количество преобразований, добавляя больше становится привлекательным быстро.
посмотреть это технический документ для более тщательного обсуждения того, как работает DMS, и как он используется для применения преобразований инструментария, как вы хотите сделать, в реальных инструментах. В данной статье описаны основные идеи, лежащие в основе инструментов тестового покрытия, продаваемых семантическими конструкциями.
после разбора текста на этой странице есть отличная информация о компиляции и динамическом выполнении кода:http://www.west-wind.com/presentations/dynamiccode/dynamiccode.htm