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(抽象语法树)开始