SwiftSyntax詳解

SwiftSyntax是基於libSyntax構建的Swift庫,利用它能夠分析,生成和轉換Swift代碼。如今已經有一些基於它開源的庫,好比SwiftRewriter針對代碼進行自動格式化(其中包括基於代碼規範進行簡單的代碼優化)。前端

Swift 編譯器

Swift編譯器分爲前端和後端,LLVM架構下的都是如此(Objective-C編譯器的前端是Clang,後端是也是LLVM),下圖是Swift的編譯器結構:node

swift-compilation-diagram-8af7d0078f72cdaa8f50430e608f15a9d4214f5772439d2fd6904bb5a8a53c60.png

下面來解釋一下各個階段python

階段 解釋 做用
Parse 語法分析 語法分析器對Swift源碼進行逐字分析,生成不包含語義和類型信息的抽象語法樹,簡稱AST(Abstract Syntax Tree)。這個階段生成的AST也不包含警告和錯誤的注入。
Sema 語義分析 語義分析器會進行工做並生成一個經過類型檢查的AST,而且在源碼中嵌入警告和錯誤等信息
SILGen Swift中級語言生成 Swift中級語言生成(SILGen)階段將經過語義分析生成的AST轉換爲Raw SIL,再對Raw SIL進行了一些優化(例如泛型特化,ARC優化等)以後生成了Canonical SIL。SIL是Swift定製的中間語言,針對Swift進行了大量的優化,使得Swift性能獲得提高。SIL也是Swift編譯器的精髓所在。
IRGen 生成LLVM的中間語言 將SIL降級爲LLVM IR,LLVM的中間語言
LLVM LLVM編譯器架構下的後端 前面幾個階段屬於Swift編譯器,至關於OC中的Clang,屬於LLVM編譯器架構下的前端,這裏的LLVM是編譯器架構下的後端,對LLVM IR進一步優化並生成目標文件(.o)

SwiftSyntax

SwiftSyntax 的操做目標是編譯過程第一步所生成的 AST,從上面瞭解到AST不包含語義和類型信息,本文的關注點也是AST,其實生成AST須要兩步:git

第一步,詞法分析,也叫作掃描scanner(或者Lexer)。它讀取咱們的代碼,而後把它們按照預約的規則合併成一個個的標識tokens。同時,它會移除空白符,註釋等。最後,整個代碼將被分割進一個tokens列表(或者說一維數組)。github

當詞法分析源代碼的時候,它會一個一個字母地讀取代碼,因此很形象地稱之爲掃描-scans;當它遇到空格,操做符,或者特殊符號的時候,它會認爲一個話已經完成了。express

Screen Shot 2019-10-20 at 8.01.33 PM.png

第二步,語法分析,也解析器。它會將詞法分析出來的數組轉化成樹形的表達形式。當生成樹的時候,解析器會刪除一些不必的標識tokens(好比不完整的括號),所以AST不是100%與源碼匹配的,可是已經能讓咱們知道如何處理了。編程

xcrun swiftc -frontend -emit-syntax ./Cat.swift | python -m json.tool
複製代碼

能夠在終端使用這個命令,結果爲一串 JSON 格式的 AST,我截取了其中一部分,把開頭import相關的移除了。json

{
    "id": 28,
    "kind": "SourceFile",
    "layout": [
        {
            "id": 27,
            "kind": "CodeBlockItemList",
            "layout": [
                {
                    "id": 25,
                    "kind": "CodeBlockItem",
                    "layout": [
                        {
                            "id": 24,
                            "kind": "StructDecl",
                            "layout": [
                                null,
                                null,
                                {
                                    "id": 7,
                                    "leadingTrivia": [
                                        {
                                            "kind": "Newline",
                                            "value": 2
                                        }
                                    ],
                                    "presence": "Present",
                                    "tokenKind": {
                                        "kind": "kw_struct"
                                    },
                                    "trailingTrivia": [
                                        {
                                            "kind": "Space",
                                            "value": 1
                                        }
                                    ]
                                },
                                {
                                    "id": 8,
                                    "leadingTrivia": [],
                                    "presence": "Present",
                                    "tokenKind": {
                                        "kind": "identifier",
                                        "text": "Cat"
                                    },
                                    "trailingTrivia": [
                                        {
                                            "kind": "Space",
                                            "value": 1
                                        }
                                    ]
                                },
                                null,
                                null,
                                null,
                                {
                                    "id": 23,
                                    "kind": "MemberDeclBlock",
                                    "layout": [
                                        {
                                            "id": 9,
                                            "leadingTrivia": [],
                                            "presence": "Present",
                                            "tokenKind": {
                                                "kind": "l_brace"
                                            },
                                            "trailingTrivia": []
                                        }
         
複製代碼

SwiftSyntax內部構造

RawSyntax

RawSyntax是全部Syntax的原始不可變後備存儲,表示語法樹基礎的原始樹結構。這些節點沒有身份的概念,僅提供樹的結構。它們是不可變的,能夠在語法節點之間自由共享,所以它們不維護任何父母關係。最終,RawSyntax在以TokenSyntax類表示的Token中達到最低點,也就是葉子節點。swift

  • RawSyntax 是全部語法的不可變後備存儲。
  • RawSyntax 是不可變的。
  • RawSyntax 創建語法的樹結構。
  • RawSyntax 不存儲任何父母關係,所以若是語法節點具備相同的內容,則能夠在語法節點之間共享它們。
final class RawSyntax: ManagedBuffer<RawSyntaxBase, RawSyntaxDataElement> {
	let data: RawSyntaxData
	var presence: SourcePresence
}

/// 特定樹或者Token節點的數據
fileprivate enum RawSyntaxData {
  /// 一個token,包含tokenKind,leading trivia, and trailing trivia
  case token(TokenData)
  /// 一個樹節點,包含syntaxKind和一個子節點數組
  case layout(LayoutData)
}
複製代碼

Trivia

Trivia與程序的語義無關,如下是一些Trivia的「原子」例子:後端

  • 空格
  • 標籤
  • 換行符
  • // 註釋
  • /* ... */ 註釋
  • /// 註釋
  • /** ... */ 註釋
  • ` ` 反引號

解析或構造新的語法節點時,應遵循如下兩個Trivia規則:

  1. Trailing trivia: 一個Token擁有它以後的全部Trivia,直到遇到下一個換行符,而且不包含這個換行符。
  2. Leading trivia: 一個Token擁有它以前的全部Trivia,直到遇到第一個換行符,而且包含這個換行符。

例子

func foo() {
  var x = 2
}
複製代碼

咱們來逐個Token分解

  • func
  • Leading trivia: 無

  • Trailing trivia: 佔有以後的一個空格(根據規則1)

    // Equivalent to:
    Trivia::spaces(1)
    複製代碼
  • foo
  • Leading trivia: 無,前一個func佔有了這個空格
  • Trailing trivia: 無
  • (
  • Leading trivia: 無
  • Trailing trivia: 無
  • )
  • Leading trivia: 無
  • Trailing trivia: 佔有以後的一個空格(根據規則1)
  • {
  • Leading trivia: 無,前一個(佔有了這個空格
  • Trailing trivia: 無,不佔用下一個換行符(根據規則1)
  • var
  • Leading trivia: 一個換行符和兩個空格(根據規則2)

    ```    
       // Equivalent to:
      Trivia::newlines(1) + Trivia::spaces(2)
    ```
    複製代碼
  • Trailing trivia: 佔有以後的一個空格(根據規則1)

  • x
  • Leading trivia: 無,前一個var佔有了這個空格
  • Trailing trivia: 佔有以後的一個空格(根據規則1)
  • =
  • Leading trivia: 無,前一個x佔有了這個空格
  • Trailing trivia: 佔有以後的一個空格(根據規則1)
  • 2
  • Leading trivia: 無,前一個=佔有了這個空格
  • Trailing trivia: 無,不佔用下一個換行符(根據規則1)
  • }
  • Leading trivia: 一個換行符(根據規則2)
  • Trailing trivia: 無
  • EOF
  • Leading trivia: 無
  • Trailing trivia: 無

SyntaxData

它用一些附加信息包裝RawSyntax節點:指向父節點的指針,該節點在其父節點中的位置以及緩存的子節點。能夠將SyntaxData視爲「具體「或「已實現」語法節點。它們表明特定的源代碼片斷,具備絕對的位置,行和列號等。SyntaxData是每一個Syntax節點的基礎存儲,私有的,不對外暴露。

Syntax

Syntax表示在葉子上帶有Token的節點樹,每一個節點都有其已知子節點的訪問器,並容許經過其children屬性對子節點進行有效的迭代。

抽象語法樹節點的類別有三個類:與聲明有關、與表達式有關、與語句有關。Swift也是同樣,只不過在實現的時候劃分更加細

public protocol DeclSyntax: Syntax {}

public protocol ExprSyntax: Syntax {}

public protocol StmtSyntax: Syntax {}

public protocol TypeSyntax: Syntax {}

public protocol PatternSyntax: Syntax {}

複製代碼
  • DeclSyntax:與聲明有關,好比TypealiasDeclSyntax、ClassDeclSyntax、StructDeclSyntax、ProtocolDeclSyntax、ExtensionDeclSyntax、FunctionDeclSyntax、DeinitializerDeclSyntax、ImportDeclSyntax、VariableDeclSyntax、EnumCaseDeclSyntax等等。
  • StmtSyntax:與語句有關,好比GuardStmtSyntax、ForInStmtSyntax、SwitchStmtSyntax、DoStmtSyntax、BreakStmtSyntax、ReturnStmtSyntax等等。
  • ExprSyntax:與表達式有關,好比StringLiteralExprSyntax、IntegerLiteralExprSyntax、TryExprSyntax、FloatLiteralExprSyntax、TupleExprSyntax、DictionaryExprSyntax等等。
  • TypeSyntax:與聲明有關,表示類型,TupleTypeSyntax、FunctionTypeSyntax、DictionaryTypeSyntax、ArrayTypeSyntax、ClassRestrictionTypeSyntax、AttributedTypeSyntax等
  • PatternSyntax:與模式匹配有關

swift中模式有如下幾種:

  • 通配符模式(WildcardPatternSyntax)
  • 標識符模式(IdentifierPatternSyntax)
  • 值綁定模式(ValueBindingPatternSyntax)
  • 元組模式(TuplePatternSyntax)
  • 枚舉用例模式(EnumCasePatternSyntax)
  • 可選模式(OptionalPatternSyntax)
  • 類型轉換模式(AsTypePatternSyntax)
  • 表達式模式(ExpressionPatternSyntax)
  • 未知模式(UnknownPatternSyntax)

除了以上幾種大類型的Syntax,還有其餘的Syntax:

  • SourceFileSyntax
  • FunctionParameterSyntax
  • InitializerClauseSyntax
  • MemberDeclListItemSyntax
  • MemberDeclBlockSyntax
  • TypeInheritanceClauseSyntax
  • InheritedTypeSyntax
  • ......

SyntaxNode

表示語法樹中的節點。這是比Syntax更有效的表示形式,由於它避免了對錶示父層次結構的Syntax的強制轉換。它提供通常信息,例如節點的位置,範圍和uniqueIdentifier,同時在必要時仍容許獲取關聯的Syntax對象。SyntaxParser使用SyntaxNode來有效地報告在增量從新解析期間從新使用了哪些語法節點。

示例:{return 1}

這是{return 1}示例圖的樣子。

SyntaxExample.png

  • 綠色:RawSyntax類型(TokenSyntax也是RawSyntax),這個圖圖是從Syntax拿來的,圖中RawTokenSyntax在SwiftSyntax是TokenSyntax。
  • 紅色:SyntaxData類型
  • 藍色:Syntax類型
  • 灰色:Trivia
  • 實心箭頭:強引用
  • 虛線箭頭:弱引用

SwiftSyntax API

Make APIs

let returnKeyword = SyntaxFactory.makeReturnKeyword(trailingTrivia: .spaces(1))
let three = SyntaxFactory.makeIntegerLiteralExpr(digits: SyntaxFactory.makeIntegerLiteral(String(3)))
let returnStmt = SyntaxFactory.makeReturnStmt(returnKeyword: returnKeyword, expression: three)

複製代碼

輸出

return 3

複製代碼

With APIs

with API用於將節點轉換爲其餘節點。 假設咱們不返回3,而是但願語句返回「hello」。咱們將使用expression方法來調用它,而後傳入字符串。

let returnHello = returnStmt.withExpression(SyntaxFactory.makeStringLiteralExpr("Hello"))

複製代碼

Syntax Builders

對於每種語法,都有一個對應的構建器結構。這些提供了一種構建語法節點的增量方法。若是咱們想從頭開始構建該cat結構,只須要四個Token,struct關鍵字,cat標識符和兩個大括號。

let structKeyword = SyntaxFactory.makeStructKeyword(trailingTrivia: .spaces(1))
let identifier = SyntaxFactory.makeIdentifier("Cat", trailingTrivia: .spaces(1))

let leftBrace = SyntaxFactory.makeLeftBraceToken()
let rightBrace = SyntaxFactory.makeRightBraceToken(leadingTrivia: .newlines(1))
let members = MemberDeclBlockSyntax { builder in
    builder.useLeftBrace(leftBrace)
    builder.useRightBrace(rightBrace)
}

let structureDeclaration = StructDeclSyntax { builder in
    builder.useStructKeyword(structKeyword)
    builder.useIdentifier(identifier)
    builder.useMembers(members)
}

複製代碼

SyntaxVisitors

使用SyntaxVisitor,咱們能夠遍歷語法樹。當咱們想要提取一些信息以對源代碼進行分析時,這頗有用。

class FindPublicExtensionDeclVisitor: SyntaxVisitor {

    func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
        if node.modifiers?.contains(where: { $0.name.tokenKind == .publicKeyword }) == true {
            // Do something if you find a `public extension` declaration.
        }
        return .skipChildren
    }
}

複製代碼

返回值是一種延續類型,指示是繼續並訪問語法樹上的子節點(.visitChildren)仍是跳過它(.skipChildren)

public enum SyntaxVisitorContinueKind {

  /// The visitor should visit the descendents of the current node.
  case visitChildren

  /// The visitor should avoid visiting the descendents of the current node.
  case skipChildren
}

複製代碼

SyntaxRewriters

SyntaxRewriter使咱們能夠經過僅重寫visit方法並基於規則返回新節點來修改樹的結構。
注意:全部節點都是不可變的,所以咱們不修改節點,而是建立另外一個節點並將其返回以替換當前節點。

class PigRewriter: SyntaxRewriter {

    override func visit(_ token: TokenSyntax) -> Syntax {
        guard case .stringLiteral = token.tokenKind else { return token }
        return token.withKind(.stringLiteral("\"🐷\""))
    }
}

複製代碼

在這個例子中,咱們將代碼中的全部字符串替換成表情🐷,官方示例是將全部數字加一,感興趣能夠去github上看看。

SwiftSyntax的使用

生成代碼

一般咱們不會用SwiftSyntax來大量生成代碼,由於這須要寫大量代碼,工做量巨大,簡直讓人崩潰😂。咱們能夠用GYBGYB(模板生成)是一個 Swift 內部使用的工具,能夠用模板生成源文件。Swift標準庫中的源碼的不少代碼就是用GYB生成的,其實SwiftSyntax不少代碼也是用GYB生成的,包括SyntaxBuilders、SyntaxFactory、SyntaxRewriter等等。開源社區中另外一個很棒的工具是 Sourcery,它容許你在Swift(經過Stencil)而不是Python中編寫模板,SwiftGen也是使用Stencil生成Swift代碼的。

分析和轉換代碼

如今有兩個不錯的庫在使用SwiftSyntax,一個是periphery,檢測未使用的Swift代碼,好比未使用的Protocol和類,以及他們的方法和方法參數等等。另外一個是SwiftRewriter,Swift代碼格式化工具。你也能夠寫一個Swift語法高亮工具,SwiftGG有這麼個例子。

參考:

Improving Swift Tools with libSyntax
An overview of SwiftSyntax
Swift編譯器結構分析
libSyntax 編程語言的實現,從AST(抽象語法樹)開始

相關文章
相關標籤/搜索