Freigeben über


Arbeiten mit der Syntax

Die Syntaxstruktur ist eine grundlegende unveränderliche Datenstruktur, die von den Compiler-APIs verfügbar gemacht wird. Diese Strukturen stellen die lexikalische und syntaktische Struktur des Quellcodes dar. Sie dienen zwei wichtigen Zwecken:

  • Um zu ermöglichen, dass Tools wie eine IDE, Add-Ins, Codeanalysetools und Refaktorisierungen die syntaktische Struktur des Quellcodes im Projekt eines Benutzers anzeigen und verarbeiten.
  • Um Tools wie Umgestaltungen und eine IDE zu aktivieren, um Quellcode auf natürliche Weise zu erstellen, zu ändern und neu anzuordnen, ohne direkte Textbearbeitungen verwenden zu müssen. Durch das Erstellen und Bearbeiten von Baumstrukturen können Tools Quellcode ganz einfach generieren und umstrukturieren.

Syntaxstrukturen

Syntaxbäume sind die primäre Struktur für die Kompilierung, Codeanalyse, Verknüpfung, Umgestaltung, IDE-Funktionen und Codegenerierung. Kein Teil des Quellcodes wird verstanden, ohne dass er zuerst identifiziert und in einem der vielen bekannten Struktursprachenelemente kategorisiert wird.

Hinweis

RoslynQuoter ist ein Open-Source-Tool, das die Syntax factory-API-Aufrufe anzeigt, die zum Erstellen der Syntaxstruktur eines Programms verwendet werden. Um es live auszuprobieren, lesen Sie http://roslynquoter.azurewebsites.net.

Syntaxstrukturen weisen drei Schlüsselattribute auf:

  • Sie enthalten alle Quellinformationen in voller Genauigkeit. Die vollständige Genauigkeit bedeutet, dass die Syntaxstruktur alle Informationen enthält, die im Quelltext gefunden werden, jedes Grammatikkonstrukt, jedes lexikalische Token und alles andere dazwischen, einschließlich Leerzeichen, Kommentare und Präprozessordirektiven. Beispielsweise wird jedes in der Quelle erwähnte Literal genau so dargestellt, wie es eingegeben wurde. Syntaxbäume erfassen auch Fehler im Quellcode, wenn das Programm unvollständig oder fehlerhaft ist, indem sie übersprungene oder fehlende Token darstellen.
  • Sie können den exakten Text, aus dem sie analysiert wurden, hervorrufen. Von jedem Syntaxknoten aus ist es möglich, die Textdarstellung der Unterstruktur abzurufen, die an diesem Knoten verwurzelt ist. Dies bedeutet, dass Syntaxstrukturen als Möglichkeit zum Erstellen und Bearbeiten von Quelltext verwendet werden können. Indem Sie einen Baum erstellen, haben Sie implizit den äquivalenten Text erstellt, und indem Sie einen neuen Baum aus Änderungen an einem vorhandenen Baum machen, haben Sie den Text effektiv bearbeitet.
  • Sie sind unveränderlich und threadsicher. Nachdem ein Baum erstellt wurde, ist er eine Momentaufnahme des aktuellen Zustands des Codes und ändert sich nie. Auf diese Weise können mehrere Benutzer gleichzeitig mit demselben Syntaxbaum in verschiedenen Threads interagieren, ohne Sperrung oder Duplikation. Da die Bäume unveränderlich sind und keine Änderungen direkt an einem Baum vorgenommen werden können, helfen Fabrikmethoden dabei, Syntaxbäume zu erstellen und zu modifizieren, indem zusätzliche Momentaufnahmen des Baums erstellt werden. Die Bäume sind effizient in der Weise, wie sie zugrunde liegende Knoten wiederverwenden, sodass eine neue Version schnell und mit wenig zusätzlichem Arbeitsspeicher neu erstellt werden kann.

Eine Syntaxstruktur ist eine Datenstruktur, in der nichtterminale Strukturelemente anderen Elementen übergeordnet sind. Jede Syntaxstruktur besteht aus Knoten, Token und Trivia.

Syntaxknoten

Syntaxknoten sind eines der primären Elemente von Syntaxstrukturen. Diese Knoten stellen syntaktische Konstrukte wie Deklarationen, Anweisungen, Klauseln und Ausdrücke dar. Jede Kategorie von Syntaxknoten wird durch eine separate Klasse dargestellt, die von Microsoft.CodeAnalysis.SyntaxNode abgeleitet wird. Der Satz von Knotenklassen ist nicht erweiterbar.

Alle Syntaxknoten sind nichtterminale Knoten in der Syntaxstruktur, was bedeutet, dass ihnen immer andere Knoten und Token untergeordnet sind. Als untergeordnetes Element eines anderen Knotens verfügt jeder Knoten über einen übergeordneten Knoten, auf den über die SyntaxNode.Parent Eigenschaft zugegriffen werden kann. Da Knoten und Strukturen unveränderlich sind, ändert sich das übergeordnete Element eines Knotens nie. Das übergeordnete Element des Stamms der Struktur ist NULL.

Jeder Knoten verfügt über eine SyntaxNode.ChildNodes() Methode, die eine Liste untergeordneter Knoten in sequenzieller Reihenfolge basierend auf ihrer Position im Quelltext zurückgibt. Diese Liste enthält keine Token. Jeder Knoten verfügt auch über Methoden zum Untersuchen von Nachfolgern, z. B. DescendantNodes, DescendantTokens oder DescendantTrivia - die eine Liste aller Knoten, Token oder Trivia darstellen, die ihren Ursprung in diesem Knoten haben.

Darüber hinaus macht jede Unterklasse der Syntaxknoten alle identischen untergeordneten Elemente durch stark typisierte Eigenschaften verfügbar. Eine Knotenklasse verfügt beispielsweise über drei zusätzliche Eigenschaften, BinaryExpressionSyntax die für binäre Operatoren spezifisch sind: Left, , OperatorTokenund Right. Der Typ von Left und Right ist ExpressionSyntax, und der Typ von OperatorToken ist SyntaxToken.

Einige Syntaxknoten haben optionale untergeordnete Elemente. Ein IfStatementSyntax verfügt beispielsweise über ein optionales ElseClauseSyntax. Die Eigenschaft gibt NULL zurück, wenn das untergeordnete Element nicht vorhanden ist.

Syntaxtoken

Syntaxtoken sind die Terminals der Sprachgrammatik, die die kleinsten syntaktischen Fragmente des Codes darstellen. Sie sind nie übergeordnete Elemente von anderen Knoten oder Token. Syntaxtoken bestehen aus Schlüsselwörtern, Bezeichnern, Literalen und Interpunktionszeichen.

Für Effizienzzwecke ist der SyntaxToken Typ ein CLR-Werttyp. Im Gegensatz zu Syntaxknoten gibt es daher nur eine Struktur für alle Arten von Token mit einer Mischung aus Eigenschaften, die je nach Art des dargestellten Tokens Bedeutung haben.

Beispielsweise stellt ein ganzzahliges Literaltoken einen numerischen Wert dar. Zusätzlich zu dem unformatierten Quelltext den das Token umfasst, verfügt das Literal-Token über eine Value-Eigenschaft, die den genauen Ganzzahlwert dekodiert angibt. Diese Eigenschaft ist als Object typisiert, weil sie einer von vielen primitiven Typen sein kann.

Die ValueText Eigenschaft informiert Sie über die gleichen Informationen wie die Value Eigenschaft. Diese Eigenschaft wird jedoch immer als Stringeingegeben. Ein Bezeichner in C#-Quelltext kann Unicode-Escapezeichen enthalten, die Syntax der Escapesequenz selbst wird jedoch nicht als Teil des Bezeichnernamens betrachtet. Obwohl der unformatierte Text, den das Token umfasst, die Escapesequenz einschließt, tut die Eigenschaft ValueText das nicht. Stattdessen enthält sie die Unicode-Zeichen, die von dem Escapezeichen identifiziert werden. Wenn beispielsweise der Quelltext einen Bezeichner enthält, der als \u03C0geschrieben wurde, gibt die ValueText Eigenschaft für dieses Token zurück π.

Syntax-Kleinigkeiten

Syntaxtrivia stellen die Teile des Quelltexts dar, die weitgehend unbedeutend für das normale Verständnis des Codes sind, z. B. Leerzeichen, Kommentare und Präprozessordirektiven. Wie Syntaxtoken sind Trivia Werttypen. Der einzelne Microsoft.CodeAnalysis.SyntaxTrivia Typ wird verwendet, um alle Arten von Trivia zu beschreiben.

Trivia werden nicht als untergeordnetes Element eines Knotens in der Syntaxstruktur eingeschlossen, weil sie kein Teil der normalen Sprachsyntax sind, und überall zwischen zwei Token auftauchen können. Obwohl sie wichtig sind, wenn ein Merkmal wie die Refaktorisierung implementiert wird, und um volle Übereinstimmung mit dem Quelltext zu wahren, sind sie als Teil des Syntaxbaums vorhanden.

Sie können auf Trivia zugreifen, indem Sie die SyntaxToken.LeadingTrivia- oder SyntaxToken.TrailingTrivia-Sammlungen eines Tokens überprüfen. Triviasequenzen werden beim Analysieren von Quelltext den Token zugeordnet. Im Allgemeinen sind einem Token alle darauffolgenden Trivia in der selben Zeile bis zum nächsten Token zugeordnet. Alle Trivia nach dieser Zeile sind dem nächsten Token zugeordnet. Das erste Token in der Quelldatei erhält alle anfänglichen Trivia, und die letzte Sequenz von Trivia in der Datei wird an das Dateiende-Token angefügt, das andernfalls eine Breite von null hätte.

Im Gegensatz zu Syntaxknoten und Token haben Syntaxtrivia keine Elternteile. Da sie Teil der Struktur sind und jeweils einem einzelnen Token zugeordnet sind, können Sie auf dieses Token mit der SyntaxTrivia.Token-Eigenschaft zugreifen.

Spannweiten

Jeder Knoten, Token oder Trivia kennt seine Position innerhalb des Quelltexts und die Anzahl der Zeichen, aus denen es besteht. Eine Textposition wird als eine 32-Bit-Ganzzahl dargestellt, bei der es sich um einen nullbasierten char Index handelt. Ein TextSpan Objekt ist die Anfangsposition und eine Anzahl von Zeichen, die beide als ganze Zahlen dargestellt werden. Wenn TextSpan eine Länge von null hat, bezieht es sich auf eine Position zwischen zwei Zeichen.

Jeder Knoten verfügt über zwei TextSpan Eigenschaften: Span und FullSpan.

Die Eigenschaft Span bezeichnet die Textspanne vom ersten Token in der Unterstruktur des Knotens bis zum Ende des letzten Tokens. Diese Spanne umfasst keine führenden oder nachgestellten Trivia.

Die Eigenschaft FullSpan bezeichnet die Textspanne, die die normale Spanne des Knotens und die Spannen jeglicher führenden oder nachgestellten Trivia enthält.

Beispiel:

      if (x > 3)
      {
||        // this is bad
          |throw new Exception("Not right.");|  // better exception?||
      }

Der Anweisungsknoten innerhalb des Blocks weist eine Spanne auf, die durch die einzelnen vertikalen Balken (|) angegeben ist. Sie enthält die Zeichen throw new Exception("Not right.");. Der gesamte Bereich wird durch die doppelten vertikalen Balken (||) angegeben. Sie enthält die gleichen Zeichen wie die Spanne und Zeichen, die den führenden und nachfolgenden Trivia zugeordnet sind.

Kind-Eigenschaften

Jeder Knoten, Token oder Trivia weist eine SyntaxNode.RawKind Eigenschaft vom Typ System.Int32auf, die das genaue Syntaxelement identifiziert, das dargestellt wird. Dieser Wert kann in eine sprachspezifische Aufzählung umgewandelt werden. Jede Sprache, C# oder Visual Basic, verfügt über eine einzelne SyntaxKind-Aufzählung (Microsoft.CodeAnalysis.CSharp.SyntaxKind bzw. Microsoft.CodeAnalysis.VisualBasic.SyntaxKind), die alle möglichen Knoten, Token und Trivia-Elemente in der Grammatik auflistet. Diese Konvertierung kann automatisch erfolgen, indem auf die Erweiterungsmethoden CSharpExtensions.Kind oder die Methode VisualBasicExtensions.Kind zugegriffen wird.

Die RawKind Eigenschaft ermöglicht eine einfache Disambiguierung von Syntaxknotentypen, die derselben Knotenklasse angehören. Bei Token und Trivia ist diese Eigenschaft die einzige Möglichkeit, einen Elementtyp von einem anderen zu unterscheiden.

Angenommen, eine einzelne BinaryExpressionSyntax-Klasse verfügt über die untergeordneten Elemente Left, OperatorToken und Right. Die Kind-Eigenschaft unterscheidet, ob es sich um einen AddExpression, SubtractExpression oder MultiplyExpression-Syntaxknoten handelt.

Tipp

Es wird empfohlen, Typen mithilfe von IsKind (für C#) oder IsKind (für VB)-Erweiterungsmethoden zu überprüfen.

Irrtümer

Auch wenn der Quelltext Syntaxfehler enthält, wird eine vollständige Syntaxstruktur verfügbar gemacht, die auf die Quelle zurückführbar ist. Wenn der Parser code findet, der nicht der definierten Syntax der Sprache entspricht, wird eine von zwei Techniken zum Erstellen einer Syntaxstruktur verwendet:

  • Wenn der Parser eine bestimmte Art von Token erwartet, aber nicht findet, fügt es möglicherweise ein fehlendes Token in die Syntaxstruktur an der Stelle ein, an der das Token erwartet wurde. Ein fehlendes Token stellt das tatsächliche Token dar, das erwartet wurde, hat aber einen leeren Bereich, und seine SyntaxNode.IsMissing-Eigenschaft gibt true zurück.

  • Der Parser kann Token überspringen, bis es findet, wo er die Analyse fortsetzen kann. In diesem Fall werden die übersprungenen Token als Trivia-Knoten mit dem Typ SkippedTokensTrivia angefügt.