SwiftSyntax是基於libSyntax構建的Swift庫,利用它能夠分析,生成和轉換Swift代碼。如今已經有一些基於它開源的庫,好比SwiftRewriter針對代碼進行自動格式化(其中包括基於代碼規範進行簡單的代碼優化)。前端
Swift編譯器分爲前端和後端,LLVM架構下的都是如此(Objective-C編譯器的前端是Clang,後端是也是LLVM),下圖是Swift的編譯器結構:node
下面來解釋一下各個階段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 的操做目標是編譯過程第一步所生成的 AST,從上面瞭解到AST不包含語義和類型信息,本文的關注點也是AST,其實生成AST須要兩步:git
第一步,詞法分析,也叫作掃描scanner(或者Lexer)。它讀取咱們的代碼,而後把它們按照預約的規則合併成一個個的標識tokens。同時,它會移除空白符,註釋等。最後,整個代碼將被分割進一個tokens列表(或者說一維數組)。github
當詞法分析源代碼的時候,它會一個一個字母地讀取代碼,因此很形象地稱之爲掃描-scans;當它遇到空格,操做符,或者特殊符號的時候,它會認爲一個話已經完成了。express
第二步,語法分析,也解析器。它會將詞法分析出來的數組轉化成樹形的表達形式。當生成樹的時候,解析器會刪除一些不必的標識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": []
}
複製代碼
RawSyntax是全部Syntax的原始不可變後備存儲,表示語法樹基礎的原始樹結構。這些節點沒有身份的概念,僅提供樹的結構。它們是不可變的,能夠在語法節點之間自由共享,所以它們不維護任何父母關係。最終,RawSyntax在以TokenSyntax類表示的Token中達到最低點,也就是葉子節點。swift
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規則:
例子
func foo() {
var x = 2
}
複製代碼
咱們來逐個Token分解
func
Leading trivia: 無
Trailing trivia: 佔有以後的一個空格(根據規則1)
// Equivalent to:
Trivia::spaces(1)
複製代碼
foo
func
佔有了這個空格(
)
{
(
佔有了這個空格var
Leading trivia: 一個換行符和兩個空格(根據規則2)
```
// Equivalent to:
Trivia::newlines(1) + Trivia::spaces(2)
```
複製代碼
Trailing trivia: 佔有以後的一個空格(根據規則1)
x
var
佔有了這個空格=
x
佔有了這個空格2
=
佔有了這個空格}
EOF
它用一些附加信息包裝RawSyntax節點:指向父節點的指針,該節點在其父節點中的位置以及緩存的子節點。能夠將SyntaxData視爲「具體「或「已實現」語法節點。它們表明特定的源代碼片斷,具備絕對的位置,行和列號等。SyntaxData是每一個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 {}
複製代碼
swift中模式有如下幾種:
除了以上幾種大類型的Syntax,還有其餘的Syntax:
表示語法樹中的節點。這是比Syntax更有效的表示形式,由於它避免了對錶示父層次結構的Syntax的強制轉換。它提供通常信息,例如節點的位置,範圍和uniqueIdentifier
,同時在必要時仍容許獲取關聯的Syntax
對象。SyntaxParser
使用SyntaxNode
來有效地報告在增量從新解析期間從新使用了哪些語法節點。
這是{return 1}
示例圖的樣子。
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 API用於將節點轉換爲其餘節點。 假設咱們不返回3,而是但願語句返回「hello」。咱們將使用expression方法來調用它,而後傳入字符串。
let returnHello = returnStmt.withExpression(SyntaxFactory.makeStringLiteralExpr("Hello"))
複製代碼
對於每種語法,都有一個對應的構建器結構。這些提供了一種構建語法節點的增量方法。若是咱們想從頭開始構建該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)
}
複製代碼
使用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
}
複製代碼
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來大量生成代碼,由於這須要寫大量代碼,工做量巨大,簡直讓人崩潰😂。咱們能夠用GYB,GYB(模板生成)是一個 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(抽象語法樹)開始