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


вам нужно использовать деревья выражений. Некоторая полезная информация от MSDN для начала:


для разбора вы можете использовать Класс CSharpCodeProviderС разбора() .


после разбора текста на этой странице есть отличная информация о компиляции и динамическом выполнении кода:http://www.west-wind.com/presentations/dynamiccode/dynamiccode.htm