語法樹狀結構是編譯程式 API 公開的基本不可變數據結構。 這些樹狀結構代表原始程式碼的語彙和語法結構。 它們有兩個重要用途:
- 若要允許工具,例如 IDE、附加元件、程式代碼分析工具和重構工具,查看並處理使用者專案中原始程式碼的語法結構。
- 若要啟用像是重構工具及 IDE 這類工具,自然地進行建立、修改和重構原始程式碼,無需直接編輯文字。 藉由建立及操作樹,工具可以輕鬆地建立及重新排列原始程式碼。
語法樹
語法樹狀結構是用於編譯、程序代碼分析、系結、重構、IDE 功能和程式代碼產生的主要結構。 在未先識別並分類成許多已知結構語言元素之一的情況下,就無法瞭解原始程式碼的一部分。
備註
RoslynQuoter 是一個開放原始碼工具,用於顯示用來建構程式語法樹狀結構的語法工廠 API 呼叫。 若要即時試用,請參閱 http://roslynquoter.azurewebsites.net。
語法樹狀結構有三個主要屬性:
- 他們完整地保存所有來源資訊。 完整逼真度表示語法樹狀結構包含來源文字、每個文法建構、每個語彙標記,以及之間的其他所有資訊,包括空格符、批註和預處理器指示詞。 例如,來源中所提及的每個字面值都以輸入時的方式完全呈現。 當程式不完整或格式不正確時,語法樹狀結構也會擷取原始程式碼中的錯誤,方法是表示略過或遺失的令牌。
- 他們可以產生自解析而來的確切文字。 從任何語法節點,即可取得該節點根目錄子樹的文字表示。 這項功能表示語法樹狀結構可用來建構和編輯來源文字。 藉由建立一棵樹,您已經暗指地創建了對等的文字,並且藉由在現有樹上進行變更來創建新樹,您已經有效地編輯了文字。
- 它們是不可變的且執行緒安全的。 取得樹狀結構之後,它是程式代碼目前狀態的快照集,且永遠不會變更。 這可讓多個使用者在不同線程中同時與相同的語法樹狀結構互動,而不會鎖定或重複。 因為樹狀結構不可變,而且無法直接修改樹狀結構,所以 Factory 方法可藉由建立樹狀結構的其他快照,協助建立和修改語法樹狀結構。 樹狀結構透過重用基礎節點來提升效率,因此可以快速重建新版本,且只需很少的額外記憶體。
語法樹狀結構實際上是樹狀結構數據結構,其中非終端式結構元素會父代其他元素。 每個語法樹狀結構是由節點、令牌和瑣事所組成。
語法節點
語法節點是語法樹狀結構的主要元素之一。 這些節點代表語法建構,例如宣告、語句、子句和表達式。 每個語法節點類別都會以衍生自 Microsoft.CodeAnalysis.SyntaxNode的個別類別來表示。 節點類別集不可延伸。
所有語法節點都是語法樹狀結構中的非終端節點,這表示它們一律有其他節點和令牌做為子系。 作為另一個節點的子系,每個節點都有可透過 屬性存取的 SyntaxNode.Parent 父節點。 因為節點和樹狀結構不可變,所以節點的父代永遠不會變更。 樹的根部沒有父代。
每個節點都有一個 SyntaxNode.ChildNodes() 方法,它會根據來源文字中的位置,依循序傳回子節點的清單。 此清單不包含令牌。 每個節點也都有方法來檢查子系,例如 DescendantNodes、 DescendantTokens或 DescendantTrivia ,代表存在於該節點根目錄之子樹中的所有節點、令牌或瑣事清單。
此外,每個語法節點子類別都會透過強型別屬性公開所有相同的子系。 例如,節點類別有三個 BinaryExpressionSyntax 二進位運算子特定的額外屬性: Left、 OperatorToken和 Right。 Left 和 Right 的類型為 ExpressionSyntax,而 OperatorToken 的類型為 SyntaxToken。
某些語法節點有選擇性的子系。 例如,IfStatementSyntax 可以具有選擇性ElseClauseSyntax。 如果子系不存在,屬性會傳回 null。
語法符號
語法標記是語言文法的終端機,代表程式代碼的最小語法片段。 它們絕不是其他節點或令牌的父代。 語法令牌是由關鍵詞、標識碼、常值和標點符號所組成。
為了提高效率,此 SyntaxToken 類型為CLR實值類型。 因此,與語法節點不同,各種令牌只有一個結構,而且屬性混合在一起,其意義取決於所表示的令牌類型。
例如,整數常值標記代表數值。 除了文字令牌跨越的原始文本之外,文字令牌還有一個 Value 屬性,告訴您確切解碼的整數值。 此屬性的類型被打為Object,因為它可能是許多基本類型之一。
屬性 ValueText 告訴您的資訊與屬性 Value 相同;但是此屬性一律為 String 類型。 C# 來源文字中的識別碼可能包含 Unicode 逸出字元,但逸出序列本身的語法不會被視為標識碼名稱的一部分。 因此,雖然標記所跨越的原始文字確實包含逸出序列,但 ValueText 屬性不會。 而是包含透過跳脫序列所識別的 Unicode 字元。 例如,如果來源文字包含寫入為 \u03C0
的識別碼,則 ValueText 此令牌的 屬性會傳回 π
。
語法瑣事
語法細節代表程式碼中的部分內容,像空白符、註解和前置處理指令,這些對於一般理解程式碼而言並不重要。 如同語法標記,Trivia 是值類型。 單一 Microsoft.CodeAnalysis.SyntaxTrivia 類型可用來描述各種瑣事。
由於註解不是語言語法常規的一部分,而且可以在任意兩個語法標記之間出現,所以它們不會作為節點的子節包含在語法樹中。 然而,因為它們在實作重構等功能時很重要,而且為了維持來源文字的完整逼真度,所以它們確實存在於語法樹狀結構中。
您可以藉由檢查令牌的SyntaxToken.LeadingTrivia或SyntaxToken.TrailingTrivia集合來存取附加資料。 剖析來源文字時,序列細節會與標記相關聯。 一般而言,令牌會擁有同一行中跟在它後面的附註,直到下一個令牌。 該行之後的任何雜項都與下列令牌相關聯。 來源檔案中的第一個令牌會取得所有初始的附加資訊,而檔案中最後一連串的附加資訊會附加到檔尾令牌上,否則該令牌的寬度為零。
與語法節點和令牌不同,語法 Trivia 沒有父代。 不過,因為它們是樹狀結構的一部分,而且每個令牌都與單一令牌相關聯,所以您可以使用 屬性來存取它相關聯的 SyntaxTrivia.Token 令牌。
範圍
每個節點、令牌或 Trivia 都會知道其在來源文字中的位置,以及它所包含的字元數目。 文字位置會以32位整數表示,這是以零起始 char
的索引。
TextSpan對像是開頭位置和字元計數,兩者都以整數表示。 如果 TextSpan 長度為零,則表示兩個字元之間的位置。
每個節點都有兩個 TextSpan 屬性: Span 和 FullSpan。
屬性 Span 是從節點子樹中第一個令牌的開頭到最後一個令牌結尾的文字範圍。 此範圍不包含任何前置或尾端的瑣碎細節。
屬性 FullSpan 是包含節點正常範圍的文字範圍,以及任何前置或尾端附加內容的範圍。
例如:
if (x > 3)
{
|| // this is bad
|throw new Exception("Not right.");| // better exception?||
}
區塊內的語句節點具有單一垂直線 (|) 所表示的範圍。 包含字元 throw new Exception("Not right.");
。 完整範圍是由雙垂直線 (||) 表示。 它包含與範圍相同的字元,以及與前後附加資訊相關的字元。
種
每個節點、令牌或 Trivia 都有一個 SyntaxNode.RawKind 屬性,其類型為 System.Int32,可識別所代表的確切語法元素。 這個值可以轉換成語言特有的列舉。 每種語言,C# 或 Visual Basic,都有一個列舉(SyntaxKind
和 Microsoft.CodeAnalysis.CSharp.SyntaxKind),分別列出文法中所有可能的節點、標記和 Trivia 元素。 您可以藉由存取 CSharpExtensions.Kind 或 VisualBasicExtensions.Kind 擴充方法,自動完成此轉換。
屬性 RawKind 可讓您輕鬆釐清共用相同節點類別的語法節點類型。 對於令牌和 Trivia,這個屬性是區別一種元素類型與另一種元素的唯一方式。
例如,單 BinaryExpressionSyntax 一類別具有 Left、 OperatorToken和 Right 作為子系。 屬性 Kind 用來區分它是 AddExpression、SubtractExpression,或 MultiplyExpression 類型的語法節點。
錯誤
即使來源文字包含語法錯誤,也會公開可往返來源的完整語法樹狀結構。 當剖析器遇到不符合語言所定義語法的程式代碼時,它會使用兩種技術之一來建立語法樹狀結構:
如果剖析器預期特定類型的令牌,但找不到它,可能會在預期的位置插入缺失的令牌到語法樹中。 遺漏的令牌代表預期的實際令牌,但它具有空範圍,其 SyntaxNode.IsMissing 屬性會返回
true
。剖析器可能會略過令牌,直到找到使其能夠繼續剖析的令牌為止。 在此案例中,跳過的標記會附加為具有類型 SkippedTokensTrivia 的附加訊息節點。