共用方式為


處理語法

語法樹狀結構是編譯程式 API 公開的基本不可變數據結構。 這些樹狀結構代表原始程式碼的語彙和語法結構。 它們有兩個重要用途:

  • 若要允許工具,例如 IDE、附加元件、程式代碼分析工具和重構工具,查看並處理使用者專案中原始程式碼的語法結構。
  • 若要啟用像是重構工具及 IDE 這類工具,自然地進行建立、修改和重構原始程式碼,無需直接編輯文字。 藉由建立及操作樹,工具可以輕鬆地建立及重新排列原始程式碼。

語法樹

語法樹狀結構是用於編譯、程序代碼分析、系結、重構、IDE 功能和程式代碼產生的主要結構。 在未先識別並分類成許多已知結構語言元素之一的情況下,就無法瞭解原始程式碼的一部分。

備註

RoslynQuoter 是一個開放原始碼工具,用於顯示用來建構程式語法樹狀結構的語法工廠 API 呼叫。 若要即時試用,請參閱 http://roslynquoter.azurewebsites.net

語法樹狀結構有三個主要屬性:

  • 他們完整地保存所有來源資訊。 完整逼真度表示語法樹狀結構包含來源文字、每個文法建構、每個語彙標記,以及之間的其他所有資訊,包括空格符、批註和預處理器指示詞。 例如,來源中所提及的每個字面值都以輸入時的方式完全呈現。 當程式不完整或格式不正確時,語法樹狀結構也會擷取原始程式碼中的錯誤,方法是表示略過或遺失的令牌。
  • 他們可以產生自解析而來的確切文字。 從任何語法節點,即可取得該節點根目錄子樹的文字表示。 這項功能表示語法樹狀結構可用來建構和編輯來源文字。 藉由建立一棵樹,您已經暗指地創建了對等的文字,並且藉由在現有樹上進行變更來創建新樹,您已經有效地編輯了文字。
  • 它們是不可變的且執行緒安全的。 取得樹狀結構之後,它是程式代碼目前狀態的快照集,且永遠不會變更。 這可讓多個使用者在不同線程中同時與相同的語法樹狀結構互動,而不會鎖定或重複。 因為樹狀結構不可變,而且無法直接修改樹狀結構,所以 Factory 方法可藉由建立樹狀結構的其他快照,協助建立和修改語法樹狀結構。 樹狀結構透過重用基礎節點來提升效率,因此可以快速重建新版本,且只需很少的額外記憶體。

語法樹狀結構實際上是樹狀結構數據結構,其中非終端式結構元素會父代其他元素。 每個語法樹狀結構是由節點、令牌和瑣事所組成。

語法節點

語法節點是語法樹狀結構的主要元素之一。 這些節點代表語法建構,例如宣告、語句、子句和表達式。 每個語法節點類別都會以衍生自 Microsoft.CodeAnalysis.SyntaxNode的個別類別來表示。 節點類別集不可延伸。

所有語法節點都是語法樹狀結構中的非終端節點,這表示它們一律有其他節點和令牌做為子系。 作為另一個節點的子系,每個節點都有可透過 屬性存取的 SyntaxNode.Parent 父節點。 因為節點和樹狀結構不可變,所以節點的父代永遠不會變更。 樹的根部沒有父代。

每個節點都有一個 SyntaxNode.ChildNodes() 方法,它會根據來源文字中的位置,依循序傳回子節點的清單。 此清單不包含令牌。 每個節點也都有方法來檢查子系,例如 DescendantNodesDescendantTokensDescendantTrivia ,代表存在於該節點根目錄之子樹中的所有節點、令牌或瑣事清單。

此外,每個語法節點子類別都會透過強型別屬性公開所有相同的子系。 例如,節點類別有三個 BinaryExpressionSyntax 二進位運算子特定的額外屬性: LeftOperatorTokenRightLeftRight 的類型為 ExpressionSyntax,而 OperatorToken 的類型為 SyntaxToken

某些語法節點有選擇性的子系。 例如,IfStatementSyntax 可以具有選擇性ElseClauseSyntax。 如果子系不存在,屬性會傳回 null。

語法符號

語法標記是語言文法的終端機,代表程式代碼的最小語法片段。 它們絕不是其他節點或令牌的父代。 語法令牌是由關鍵詞、標識碼、常值和標點符號所組成。

為了提高效率,此 SyntaxToken 類型為CLR實值類型。 因此,與語法節點不同,各種令牌只有一個結構,而且屬性混合在一起,其意義取決於所表示的令牌類型。

例如,整數常值標記代表數值。 除了文字令牌跨越的原始文本之外,文字令牌還有一個 Value 屬性,告訴您確切解碼的整數值。 此屬性的類型被打為Object,因為它可能是許多基本類型之一。

屬性 ValueText 告訴您的資訊與屬性 Value 相同;但是此屬性一律為 String 類型。 C# 來源文字中的識別碼可能包含 Unicode 逸出字元,但逸出序列本身的語法不會被視為標識碼名稱的一部分。 因此,雖然標記所跨越的原始文字確實包含逸出序列,但 ValueText 屬性不會。 而是包含透過跳脫序列所識別的 Unicode 字元。 例如,如果來源文字包含寫入為 \u03C0的識別碼,則 ValueText 此令牌的 屬性會傳回 π

語法瑣事

語法細節代表程式碼中的部分內容,像空白符、註解和前置處理指令,這些對於一般理解程式碼而言並不重要。 如同語法標記,Trivia 是值類型。 單一 Microsoft.CodeAnalysis.SyntaxTrivia 類型可用來描述各種瑣事。

由於註解不是語言語法常規的一部分,而且可以在任意兩個語法標記之間出現,所以它們不會作為節點的子節包含在語法樹中。 然而,因為它們在實作重構等功能時很重要,而且為了維持來源文字的完整逼真度,所以它們確實存在於語法樹狀結構中。

您可以藉由檢查令牌的SyntaxToken.LeadingTriviaSyntaxToken.TrailingTrivia集合來存取附加資料。 剖析來源文字時,序列細節會與標記相關聯。 一般而言,令牌會擁有同一行中跟在它後面的附註,直到下一個令牌。 該行之後的任何雜項都與下列令牌相關聯。 來源檔案中的第一個令牌會取得所有初始的附加資訊,而檔案中最後一連串的附加資訊會附加到檔尾令牌上,否則該令牌的寬度為零。

與語法節點和令牌不同,語法 Trivia 沒有父代。 不過,因為它們是樹狀結構的一部分,而且每個令牌都與單一令牌相關聯,所以您可以使用 屬性來存取它相關聯的 SyntaxTrivia.Token 令牌。

範圍

每個節點、令牌或 Trivia 都會知道其在來源文字中的位置,以及它所包含的字元數目。 文字位置會以32位整數表示,這是以零起始 char 的索引。 TextSpan對像是開頭位置和字元計數,兩者都以整數表示。 如果 TextSpan 長度為零,則表示兩個字元之間的位置。

每個節點都有兩個 TextSpan 屬性: SpanFullSpan

屬性 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,都有一個列舉(SyntaxKindMicrosoft.CodeAnalysis.CSharp.SyntaxKind),分別列出文法中所有可能的節點、標記和 Trivia 元素。 您可以藉由存取 CSharpExtensions.KindVisualBasicExtensions.Kind 擴充方法,自動完成此轉換。

屬性 RawKind 可讓您輕鬆釐清共用相同節點類別的語法節點類型。 對於令牌和 Trivia,這個屬性是區別一種元素類型與另一種元素的唯一方式。

例如,單 BinaryExpressionSyntax 一類別具有 LeftOperatorTokenRight 作為子系。 屬性 Kind 用來區分它是 AddExpressionSubtractExpression,或 MultiplyExpression 類型的語法節點。

小提示

建議使用 IsKind (適用於 C#) 或 IsKind (適用於 VB) 擴充方法來檢查種類。

錯誤

即使來源文字包含語法錯誤,也會公開可往返來源的完整語法樹狀結構。 當剖析器遇到不符合語言所定義語法的程式代碼時,它會使用兩種技術之一來建立語法樹狀結構:

  • 如果剖析器預期特定類型的令牌,但找不到它,可能會在預期的位置插入缺失的令牌到語法樹中。 遺漏的令牌代表預期的實際令牌,但它具有空範圍,其 SyntaxNode.IsMissing 屬性會返回 true

  • 剖析器可能會略過令牌,直到找到使其能夠繼續剖析的令牌為止。 在此案例中,跳過的標記會附加為具有類型 SkippedTokensTrivia 的附加訊息節點。