Korzystanie ze składni
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 zezwolić na narzędzia — takie jak środowisko IDE, dodatki, narzędzia do analizy kodu i refaktoryzacje — aby wyświetlić i przetworzyć strukturę składniową kodu źródłowego w projekcie użytkownika.
- Aby włączyć narzędzia , takie jak refaktoryzacje i środowisko IDE, umożliwia tworzenie, modyfikowanie i rozmieszczanie kodu źródłowego w naturalny sposób bez konieczności używania bezpośrednich edycji 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
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 rooted 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, którego dotyczy, przez implikację, utworzono odpowiedni tekst i wprowadzając nowe drzewo ze zmian w istniejącym drzewie, tekst został skutecznie edytowany.
- 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 drzewie, metody fabryki pomagają tworzyć i modyfikować drzewa składni, tworząc dodatkowe migawki drzewa. Drzewa są wydajne w sposób ponownego użycia węzłów bazowych, dzięki czemu nowa wersja może zostać szybko skompilowana i z małą ilością pamięci.
Drzewo składni jest dosłownie strukturą danych drzewa, w której elementy strukturalne nie terminalu mają inne elementy. 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ę pochodzącą z Microsoft.CodeAnalysis.SyntaxNodeklasy . 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 . Ponieważ węzły i drzewa są niezmienne, element nadrzędny węzła nigdy się nie zmienia. Katalog główny drzewa ma element nadrzędny 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, DescendantTokenslub DescendantTrivia , które reprezentują listę wszystkich węzłów, tokenów lub trywii, które istnieją w poddrzewie rooted 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 i LeftRight to ExpressionSyntax, a typ OperatorToken to SyntaxToken.
Niektóre węzły składni mają opcjonalne elementy podrzędne. Na przykład element IfStatementSyntax ma opcjonalny element ElseClauseSyntax. Jeśli element podrzędny nie jest obecny, 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.
W celach SyntaxToken wydajności typ 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 token obejmuje token literału ma Value właściwość, która informuje o dokładnej zdekodowanej wartości całkowitej. Ta właściwość jest typowana, ponieważ Object 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 nieprzetworzonego tekstu obejmującego token zawiera sekwencję ucieczki, ValueText właściwość nie jest. 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ść π
.
Trivia składniowa
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 trywii, sprawdzając tokeny SyntaxToken.LeadingTrivia lub SyntaxToken.TrailingTrivia kolekcje. Gdy tekst źródłowy jest analizowany, sekwencje trywii są skojarzone z tokenami. Ogólnie rzecz biorąc, token jest właścicielem wszelkich trywii po nim w tym samym wierszu do następnego tokenu. Wszelkie trywii po tym wierszu są skojarzone z następującym tokenem. Pierwszy token w pliku źródłowym pobiera wszystkie początkowe trivia, a ostatnia sekwencja trywii w pliku jest tacked na końcu tokenu pliku, który w przeciwnym razie ma zerową szerokość.
W przeciwieństwie do węzłów składni i tokenów, składnia nie ma elementów nadrzędnych. Jednak ponieważ są one częścią drzewa i każdy jest skojarzony z jednym tokenem, możesz uzyskać dostęp do tokenu skojarzonego z użyciem SyntaxTrivia.Token właściwości .
Obejmuje
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
. TextSpan Obiekt jest pozycją początkową i liczbą znaków, zarówno reprezentowaną jako liczba całkowita. 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 wiodących ani końcowych trywii.
Właściwość FullSpan jest zakresem tekstowym obejmującym normalny zakres węzła oraz zakres wszelkich triwi wiodących lub końcowych.
Na 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 gramatyki. Tę konwersję można wykonać automatycznie, korzystając CSharpExtensions.Kind z metod lub VisualBasicExtensions.Kind rozszerzeń.
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 BinaryExpressionSyntax klasa ma Leftelement , OperatorTokeni Right jako elementy podrzędne. Właściwość Kind rozróżnia, czy jest AddExpressionto węzeł składni , SubtractExpression, czy MultiplyExpression też typu .
Napiwek
Zaleca się sprawdzenie rodzajów przy użyciu IsKind metod rozszerzeń (dla języka C#) lub IsKind (w przypadku języka VB).
Błędy
Nawet jeśli tekst źródłowy zawiera błędy składni, uwidocznione jest pełne drzewo składni, które można objazdać do źródła. 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 lokalizacji oczekiwanej przez token. 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.