Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Drzewo składni jest podstawową niezmienną strukturą danych uwidacznianą przez interfejsy API kompilatora. Drzewa te reprezentują leksykatyczną i składniową strukturę kodu źródłowego. Służą one dwóm ważnym celom:
- Aby umożliwić narzędziom, takim jak środowisko IDE, dodatki, narzędzia do analizy kodu i refaktoryzacje, dostęp do struktury składniowej kodu źródłowego w projekcie użytkownika i jej przetwarzanie.
- Aby narzędzia, takie jak refaktoryzacje i IDE, mogły tworzyć, modyfikować i rozmieszczać kod źródłowy w sposób naturalny, bez konieczności bezpośredniego edytowania tekstu. Tworząc drzewa i manipulując nimi, narzędzia mogą łatwo tworzyć i rozmieszczać kod źródłowy.
Drzewa składni
Drzewa składni to podstawowa struktura używana do kompilacji, analizy kodu, powiązania, refaktoryzacji, funkcji IDE i generowania kodu. Żadna część kodu źródłowego nie jest rozumiana bez uprzedniego identyfikowania i kategoryzowania w jednym z wielu dobrze znanych elementów języka strukturalnego.
Uwaga / Notatka
RoslynQuoter to narzędzie typu open source, które pokazuje wywołania interfejsu API fabryki składni używane do konstruowania drzewa składni programu. Aby wypróbować go na żywo, zobacz http://roslynquoter.azurewebsites.net.
Drzewa składni mają trzy kluczowe atrybuty:
- Przechowują wszystkie informacje źródłowe w pełnej wierności. Pełna wierność oznacza, że drzewo składni zawiera każdy element informacji znaleziony w tekście źródłowym, każdą konstrukcję gramatyczną, każdy token leksykalny i wszystkie inne elementy między nimi, w tym białe znaki, komentarze i dyrektywy preprocesora. Na przykład każdy literał wymieniony w źródle jest reprezentowany dokładnie tak, jak został wpisany. Drzewa składni przechwytują również błędy w kodzie źródłowym, gdy program jest niekompletny lub nieprawidłowo sformułowany, reprezentując pominięte lub brakujące tokeny.
- Mogą one wygenerować dokładny tekst, z którego zostały przeanalizowane. Z dowolnego węzła składni możliwe jest uzyskanie tekstowej reprezentacji poddrzewa zakorzenionego w tym węźle. Ta możliwość oznacza, że drzewa składni mogą służyć jako sposób konstruowania i edytowania tekstu źródłowego. Tworząc drzewo, w sposób dorozumiany, tworzymy równoważny tekst, a tworząc nowe drzewo na bazie zmian w istniejącym drzewie, skutecznie edytujemy tekst.
- Są niezmienne i bezpieczne wątkowo. Po uzyskaniu drzewa jest to migawka bieżącego stanu kodu i nigdy nie zmienia się. Umożliwia to wielu użytkownikom interakcję z tym samym drzewem składniowym w tym samym czasie w różnych wątkach bez blokowania lub duplikowania. Ponieważ drzewa są niezmienne i nie można wprowadzać żadnych modyfikacji bezpośrednio w drzewach, metody fabryczne pomagają w tworzeniu i modyfikowaniu drzew składni, tworząc dodatkowe wersje drzew. Drzewa są wydajne w sposobie ponownego użycia węzłów, dzięki czemu nowa wersja może zostać szybko odbudowana przy niewielkim zużyciu pamięci.
Drzewo składni jest dosłownie strukturą danych drzewa, w której nieterminalne elementy strukturalne są rodzicami innych elementów. Każde drzewo składni składa się z węzłów, tokenów i trywii.
Węzły składni
Węzły składni są jednym z podstawowych elementów drzew składniowych. Te węzły reprezentują konstrukcje składniowe, takie jak deklaracje, instrukcje, klauzule i wyrażenia. Każda kategoria węzłów składniowych jest reprezentowana przez oddzielną klasę, która jest pochodną Microsoft.CodeAnalysis.SyntaxNode. Zestaw klas węzłów nie jest rozszerzalny.
Wszystkie węzły składni są węzłami nie terminalowymi w drzewie składni, co oznacza, że zawsze mają inne węzły i tokeny jako obiekty podrzędne. Jako element podrzędny innego węzła każdy węzeł ma węzeł nadrzędny, do którego można uzyskać dostęp za pośrednictwem SyntaxNode.Parent właściwości . Węzły i drzewa są niezmienne, więc rodzic węzła nigdy się nie zmienia. Korzeń drzewa ma nadrzędnika o wartości null.
Każdy węzeł ma metodę SyntaxNode.ChildNodes() , która zwraca listę węzłów podrzędnych w kolejności sekwencyjnej na podstawie ich pozycji w tekście źródłowym. Ta lista nie zawiera tokenów. Każdy węzeł ma również metody badania elementów potomnych, takich jak DescendantNodes, DescendantTokens lub DescendantTrivia, które reprezentują listę wszystkich węzłów, tokenów lub informacji, które istnieją w poddrzewie zakorzenionym przez ten węzeł.
Ponadto każda podklasa węzła składni uwidacznia wszystkie te same elementy podrzędne za pomocą silnie typiowanych właściwości. Na przykład BinaryExpressionSyntax klasa node ma trzy dodatkowe właściwości specyficzne dla operatorów binarnych: Left, OperatorTokeni Right. Typ Left i Right jest ExpressionSyntax, a typ OperatorToken jest SyntaxToken.
Niektóre węzły składni mają opcjonalne elementy podrzędne. Na przykład element IfStatementSyntax ma opcjonalny element ElseClauseSyntax. Jeśli dziecko nie jest obecne, właściwość zwraca wartość null.
Tokeny składniowe
Tokeny składniowe to terminale gramatyki języka reprezentujące najmniejsze fragmenty składni kodu. Nigdy nie są one rodzicami innych węzłów ani tokenów. Tokeny składniowe składają się ze słów kluczowych, identyfikatorów, literałów i znaków interpunkcyjnych.
** Ze względu na wydajność, typ SyntaxToken jest typem wartości CLR. W związku z tym, w przeciwieństwie do węzłów składniowych, istnieje tylko jedna struktura dla wszystkich rodzajów tokenów z kombinacją właściwości, które mają znaczenie w zależności od rodzaju reprezentowanego tokenu.
Na przykład token literału liczb całkowitych reprezentuje wartość liczbową. Oprócz nieprzetworzonego tekstu źródłowego zakres tokenu, literał tokenu ma właściwość Value, która wskazuje na dokładną zdekodowaną wartość całkowitą. Ta właściwość jest typowana jako Object, ponieważ może być jednym z wielu typów pierwotnych.
Właściwość ValueText informuje o tych samych informacjach co Value właściwość, jednak ta właściwość jest zawsze wpisywana jako String. Identyfikator w tekście źródłowym języka C# może zawierać znaki ucieczki Unicode, ale składnia samej sekwencji ucieczki nie jest uznawana za część nazwy identyfikatora. Mimo że nieprzetworzony tekst obejmowany przez token zawiera sekwencję ucieczki, właściwość ValueText nie zawiera. Zamiast tego zawiera znaki Unicode identyfikowane przez ucieczkę. Jeśli na przykład tekst źródłowy zawiera identyfikator zapisany jako \u03C0
, ValueText właściwość tego tokenu zwróci wartość π
.
Ciekawostki składniowe
Trivia składniowa reprezentuje części tekstu źródłowego, które są w dużej mierze nieistotne dla normalnego zrozumienia kodu, takich jak białe znaki, komentarze i dyrektywy preprocesora. Podobnie jak tokeny składniowe, trivia to typy wartości. Pojedynczy Microsoft.CodeAnalysis.SyntaxTrivia typ służy do opisywania wszelkiego rodzaju trywii.
Ponieważ trivia nie jest częścią normalnej składni języka i może występować w dowolnym miejscu między dwoma tokenami, nie są one uwzględnione w drzewie składni jako element podrzędny węzła. Jednak ponieważ są one ważne podczas implementowania funkcji, takiej jak refaktoryzacja i zachowanie pełnej wierności tekstu źródłowego, istnieją one jako część drzewa składni.
Możesz uzyskać dostęp do trivia, sprawdzając kolekcje SyntaxToken.LeadingTrivia lub SyntaxToken.TrailingTrivia tokenu. Gdy tekst źródłowy jest analizowany, sekwencje trywii są skojarzone z tokenami. Ogólnie rzecz biorąc, token obejmuje wszelkie dodatkowe informacje po nim w tym samym wierszu aż do następnego tokenu. Wszelkie ciekawostki po tym wierszu zostają powiązane z następującym tokenem. Pierwszy token w pliku źródłowym pobiera wszystkie początkowe trivia, a ostatnia sekwencja trivia w pliku jest dołączona do końcowego tokenu pliku, który w przeciwnym razie ma zerową szerokość.
W przeciwieństwie do węzłów składni i tokenów, drobnostki składni nie mają elementów nadrzędnych. Jednak ponieważ są one częścią drzewa i każda jest skojarzona z jednym tokenem, możesz uzyskać dostęp do tego tokenu za pomocą właściwości SyntaxTrivia.Token.
Zakres
Każdy węzeł, token lub trivia zna swoją pozycję w tekście źródłowym i liczbę znaków, z których się składa. Pozycja tekstu jest reprezentowana jako 32-bitowa liczba całkowita, która jest indeksem zerowym char
. Obiekt TextSpan jest pozycją początkową oraz ilością znaków, obydwie są reprezentowane jako liczby całkowite. Jeśli TextSpan ma zerową długość, odwołuje się do lokalizacji między dwoma znakami.
Każdy węzeł ma dwie TextSpan właściwości: Span i FullSpan.
Właściwość Span jest zakresem tekstu od początku pierwszego tokenu w poddrzewie węzła do końca ostatniego tokenu. Ten zakres nie obejmuje żadnych początkowych ani końcowych drobnostek.
Właściwość FullSpan obejmuje przestrzeń tekstową, która zawiera normalną szerokość węzła oraz przestrzeń dla wszelkich prowadzących lub końcowych informacji pomocniczych.
Przykład:
if (x > 3)
{
|| // this is bad
|throw new Exception("Not right.");| // better exception?||
}
Węzeł instrukcji wewnątrz bloku ma zakres wskazywany przez pojedyncze pionowe słupki (|). Zawiera on znaki throw new Exception("Not right.");
. Pełny zakres jest wskazywany przez podwójne pionowe słupki (||). Zawiera on te same znaki co zakres i znaki skojarzone z wiodącymi i końcowymi trivia.
Rodzaje
Każdy węzeł, token lub trivia ma SyntaxNode.RawKind właściwość typu System.Int32, która identyfikuje dokładny element składni reprezentowany. Tę wartość można rzutować do wyliczenia specyficznego dla języka. Każdy język, C# lub Visual Basic, ma jedno SyntaxKind
wyliczenie (Microsoft.CodeAnalysis.CSharp.SyntaxKind i Microsoft.CodeAnalysis.VisualBasic.SyntaxKind, odpowiednio), które zawiera listę wszystkich możliwych węzłów, tokenów i elementów trywii w gramatyce. Tę konwersję można wykonać automatycznie, korzystając z metod rozszerzeń CSharpExtensions.Kind lub VisualBasicExtensions.Kind.
Właściwość RawKind umożliwia łatwe uściślanie typów węzłów składni współużytkujących tę samą klasę węzłów. W przypadku tokenów i trywii ta właściwość jest jedynym sposobem odróżnienia jednego typu elementu od innego.
Na przykład pojedyncza klasa BinaryExpressionSyntax ma Left, OperatorToken oraz Right jako elementy podrzędne. Właściwość Kind rozróżnia, czy jest to węzeł składni AddExpression, SubtractExpression, czy też typu MultiplyExpression.
Wskazówka
Zaleca się sprawdzenie typów przy użyciu metod rozszerzeń IsKind (dla języka C#) lub IsKind dla języka VB.
Błędy
Nawet jeśli tekst źródłowy zawiera błędy składniowe, można odtworzyć pełne drzewo składniowe z powrotem do tekstu źródłowego. Gdy analizator napotka kod, który nie jest zgodny ze zdefiniowaną składnią języka, używa jednej z dwóch technik do utworzenia drzewa składni:
Jeśli analizator oczekuje określonego rodzaju tokenu, ale go nie znajdzie, może wstawić brakujący token do drzewa składni w miejscu, gdzie się go spodziewano. Brak tokenu reprezentuje rzeczywisty token, który był oczekiwany, ale ma pusty zakres, a jego SyntaxNode.IsMissing właściwość zwraca wartość
true
.Analizator może pominąć tokeny, dopóki nie znajdzie go, w którym może kontynuować analizowanie. W takim przypadku pominięte tokeny są dołączane jako węzeł trywii z typem SkippedTokensTrivia.