Frage

Ich versuche, mit der Roslyn -API einige grundlegende Code -Differenzierung zu machen, und ich stoße auf unerwartete Probleme. Im Wesentlichen habe ich zwei Code -Teile, die gleich sind, außer dass eine Zeile hinzugefügt wurde. Dies sollte nur die Zeile des geänderten Textes zurückgeben, aber aus irgendeinem Grund sagt es mir, dass sich alles geändert hat. Ich habe auch versucht, nur eine Zeile zu bearbeiten, anstatt eine Zeile hinzuzufügen, aber ich bekomme das gleiche Ergebnis. Ich möchte dies auf zwei Versionen einer Quelldatei anwenden können, um Unterschiede zwischen den beiden zu identifizieren. Hier ist der Code, den ich gerade verwende:

        SyntaxTree tree = SyntaxTree.ParseCompilationUnit(
            @"using System;
            using System.Collections.Generic;
            using System.Linq;
            using System.Text;

            namespace HelloWorld
            {
                class Program
                {
                    static void Main(string[] args)
                    {
                        Console.WriteLine(""Hello, World!"");
                    }
                }
            }");

        var root = (CompilationUnitSyntax)tree.Root;

        var compilation = Compilation.Create("HelloWorld")
                                     .AddReferences(
                                        new AssemblyFileReference(
                                            typeof(object).Assembly.Location))
                                     .AddSyntaxTrees(tree);

        var model = compilation.GetSemanticModel(tree);
        var nameInfo = model.GetSemanticInfo(root.Usings[0].Name);
        var systemSymbol = (NamespaceSymbol)nameInfo.Symbol;

        SyntaxTree tree2 = SyntaxTree.ParseCompilationUnit(
            @"using System;
            using System.Collections.Generic;
            using System.Linq;
            using System.Text;

            namespace HelloWorld
            {
                class Program
                {
                    static void Main(string[] args)
                    {
                        Console.WriteLine(""Hello, World!"");
                        Console.WriteLine(""jjfjjf"");
                    }
                }
            }");

        var root2 = (CompilationUnitSyntax)tree2.Root;

        var compilation2 = Compilation.Create("HelloWorld")
                                     .AddReferences(
                                        new AssemblyFileReference(
                                            typeof(object).Assembly.Location))
                                     .AddSyntaxTrees(tree2);

        var model2 = compilation2.GetSemanticModel(tree2);
        var nameInfo2 = model2.GetSemanticInfo(root2.Usings[0].Name);
        var systemSymbol2 = (NamespaceSymbol)nameInfo2.Symbol;

        foreach (TextSpan t in tree2.GetChangedSpans(tree))
        {
            Console.WriteLine(tree2.Text.GetText(t));
        }

Und hier ist die Ausgabe, die ich bekomme:

System
                using System
Collections
Generic
                using System
Linq
                using System
Text

                namespace HelloWorld
                {
                    class Program
                    {
                        static
Main
args
                        {
                            Console
WriteLine
"Hello, World!"
                            Console.WriteLine("jjfjjf");
                        }
                    }
                }
Press any key to continue . . .

Interessanterweise scheint es jede Zeile als Token für jede Zeile zu zeigen, mit Ausnahme der hinzugefügten Zeile, in der sie die Linie anzeigt, ohne sie aufzubrechen. Weiß jemand, wie man die tatsächlichen Änderungen isoliert?

War es hilfreich?

Lösung

Bruce Boughtons Vermutung ist korrekt. Die GetChangedSpans-Methode soll kein allgemeiner Syntax-Differenzmechanismus sein, um den Unterschied zwischen zwei Syntaxbäumen zu nutzen, die keine gemeinsame Anamnese haben. Es ist vielmehr beabsichtigt, zwei Bäume zu nehmen, die durch Änderungen an einem gemeinsamen Baum hergestellt wurden, und zu bestimmen, welche Teile der Bäume aufgrund von Änderungen unterschiedlich sind.

Wenn Sie Ihren ersten Parse -Baum genommen und die neue Anweisung als Bearbeitung einfügt hätten, würden Sie eine weitaus kleinere Reihe von Änderungen sehen.

Es könnte helfen, wenn ich kurz beschreibe, wie die Roslyn Lexer und Parser auf hohem Niveau funktionieren.

Die Grundidee ist, dass Lexer-produzierte "Syntax-Token" und Parser-produzierte "Syntaxbäume" sind unveränderlich. Sie ändern sich nie. Weil sie sich nie ändern können, können wir Teile früherer Parse-Bäume in neuen Parse-Bäumen wiederverwenden. (Datenstrukturen mit dieser Eigenschaft werden häufig als "persistente" Datenstrukturen bezeichnet.)

Da wir vorhandene Teile wiederverwenden können, können wir beispielsweise den gleichen Wert für jede Instanz eines bestimmten Tokens verwenden, sagen wir class, das erscheint im Programm. Die Länge und Inhalte von jedem class Token ist genau das gleiche; Die einzigen Dinge, die zwei verschiedene unterscheiden class Token sind ihre Trivia, (welcher Abstand und Kommentare umgeben sie) und ihre Position, und ihre Elternteil - Welcher größere Syntaxknoten enthält das Token.

Wenn Sie einen Textblock analysieren, generieren wir Syntax -Token und Syntaxbäume in einer peristenten, unveränderlichen Form, die wir das "grüne" Form nennen. Wir wickeln dann die grünen Knoten in eine "rote" Schicht ein. Die grüne Schicht weiß nichts über Position, Eltern und so weiter. Die rote Schicht tut. (Die skurrilen Namen sind darauf zurückzuführen, dass beim ersten Zeichnen dieser Datenstruktur auf einem Whiteboard die Farben, die wir verwendet haben Die Knoten, die sich geändert haben und dann neue Knoten bauen Nur auf der Wirbelsäule der Veränderungen. Alle anderen Zweige des grünen Baumes bleiben gleich.

Wenn wir zwei Bäume differenzieren, ist das, was wir tun, im Grunde Nehmen Sie den festgelegten Unterschied der grünen Knoten. Wenn einer der Bäume durch Bearbeiten des anderen hergestellt wurde, dann Fast alle grünen Knoten werden gleich sein Weil nur die Wirbelsäule wieder aufgebaut wurde. Der Baumdifferenzalgorithmus identifiziert die geänderten Knoten und erarbeitet die betroffenen Spannweiten.

Wenn die beiden Bäume keine gemeinsame Geschichte haben, sind die einzigen grünen Knoten, die sie gemeinsam haben, die einzelnen Token, die, wie ich bereits sagte, überall wiederverwendet werden. Jeder grüne Syntaxknoten auf höherer Ebene ist ein anderer grüner Knoten und wird daher durch den Baumunterschiedsmotor als unterschiedlich behandelt, auch wenn sein Text gleich ist.

Das Zweck Von dieser Methode besteht darin, den Editorcode schnell zu erraten, welche Teile eines Textpuffer nach einem Bearbeiten oder einem Rückgängigmachen oder so etwas rekundiert werden müssen. Die Annahme ist, dass die Bäume eine historische Beziehung haben. Die Absicht ist nicht, einen allgemeinen Textunterschiedsmechanismus zu liefern. Dafür gibt es schon viele großartige Werkzeuge.

Stellen Sie sich zum Beispiel vor, Sie hätten Ihr erstes Programm in den Herausgeber eingefügt, dann das Ganze hervorgehoben und dann das zweite Programm in den Herausgeber eingefügt. Man würde vernünftigerweise erwarten, dass der Editor keine Zeit verschwenden würde, herauszufinden, welche Teile des eingeklebten Code mit dem zuvor angepassten Code identisch waren. Das könnte sehr teuer sein und die Antwort ist wahrscheinlich "nicht viel". Der Herausgeber geht vielmehr davon aus, dass die gesamte Region der Einfüge von einem brandneuen und völlig unterschiedlichen Code ist. Es verbringt keine Zeit damit, Korrespondenzen zwischen dem alten Code und dem neuen Code zu erstellen. Es repariert und rekodiert daher das Ganze.

Wenn Sie andererseits gerade in der einzelnen verschiedenen Anweisung eingefügt haben, würde die Bearbeitungsmotor die Bearbeitung einfach an den richtigen Ort einfügen. Der Parse Tree würde regeneriert werden Wiederverwenden der vorhandenen grünen Knoten nach Möglichkeit, und der Unterschied Engine würde identifizieren, was die Spannweiten neu gefärbt werden müssen: diejenigen mit unterschiedlichen grünen Knoten.

Macht das alles Sinn?

AKTUALISIEREN:

Ha, anscheinend tippten Kevin und ich beide gleichzeitig die gleiche Antwort in angrenzenden Büros ab. Ein bisschen doppelte Anstrengung, aber ich denke, beide Antworten haben gute Perspektiven auf die Situation. :-)

Andere Tipps

@brucoughton hat recht, GetChangedSpans soll Änderungen des inkrementellen Parsers entdecken. Mit Code wie folgt bekomme ich viel bessere Ausgabe:

        var code = 
        @"using System; 
        using System.Collections.Generic; 
        using System.Linq; 
        using System.Text; 

        namespace HelloWorld 
        { 
            class Program 
            { 
                static void Main(string[] args) 
                { 
                    Console.WriteLine(""Hello, World!""); 
                } 
            } 
        }";
        var text = new StringText(code);
        SyntaxTree tree = SyntaxTree.ParseCompilationUnit(text);

        var index = code.IndexOf("}");
        var added = @"    Console.WriteLine(""jjfjjf""); 
                      ";

        var code2 = code.Substring(0, index) + 
                    added +
                    code.Substring(index);

        var text2 = new StringText(code2);

        var tree2 = tree.WithChange(text2, new [] { new TextChangeRange(new TextSpan(index, 0), added.Length) } );

        foreach (var span in tree2.GetChangedSpans(tree))
        {
            Console.WriteLine(text2.GetText(span));
        }

Im Allgemeinen soll GetChangedSpans jedoch ein schneller, aber konservativer Diff sein. Für mehr Kontrolle über den Diff und genauere Ergebnisse möchten Sie wahrscheinlich Ihren eigenen Baum-Diffing-Algorithmus implementieren, den Sie auf Ihre Bedürfnisse einstellen können.

Wenn Sie VS verwenden, hat der Editor im obigen Code in der Änderung der Berichterstattung und der Textdifferenz integriert, mit der Sie das problemlos konstruieren können TextChangeRange Objekte, aber ansonsten benötigen Sie wahrscheinlich noch mindestens einen Textdifferenzalgorithmus, wenn Sie Änderungen an den inkrementellen Parser übergeben möchten.

Ich würde das vermuten GetChangedSpans soll Änderungen zwischen einem Baum und Bäumen vergleichen, die aus Änderungen am ursprünglichen Baum und nicht zwischen zwei willkürlichen Bäumen erzeugt werden.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top