内容简介:作者:Mattt,译者:
作者:Mattt, 原文链接 ,原文日期:2018-10-22
译者: jojotov ;校对: numbbbbb , Yousanflics , pmst ;定稿: Forelax
SwiftSyntax 是一个可以分析、生成以及转换 Swift 源代码的 Swift 库。它是基于 libSyntax 库开发的,并于 2017 年 8 月 从 Swift 语言的主仓库中分离出来,单独建立了一个仓库。
总的来说,这些库都是为了给结构化编辑(structured editing)提供安全、正确且直观的工具。关于结构化编辑,在 thusly 中有具体的描述:
什么是结构化编辑?结构化编辑是一种编辑的策略,它对源代码的 结构 更加敏感,而源代码的表示(例如字符或者字节)则没那么重要。这可以细化为以下几个部分:替换标识符,将对全局方法的调用转为对方法的调用,或者根据已定的规则识别并格式化整个源文件。
在写这篇文章时,SwiftSyntax 仍处于在开发中并进行 API 调整的阶段。不过目前你已经可以使用它对 Swift 代码进行一些编程工作。
目前, Swift Migrator 已经在使用 SwiftSyntax 了,并且在对内和对外层面上,对 SwiftSyntax 的接入也在不断地努力着。
SwiftSyntax 如何工作?
为了明白 SwiftSyntax 如何工作,我们首先要回头看看 Swift 编译器的架构:
Swift 编译器的主要职责是把 Swift 代码转换为可执行的机器代码。整个过程可以划分为几个离散的步骤,一开始, 语法分析器 会生成一个抽象语法树(AST)。之后,语义分析器会进行工作并生成一个通过类型检查的 AST。至此步骤,代码会降级到 Swift 中间层语言 ;随后 SIL 会继续转换并优化自身,降级为 LLVM IR ,并最终编译为机器代码。
对于我们的讨论来说,最重要的关键点是 SwiftSyntax 的操作目标是编译过程第一步所生成的 AST。但也由于这样,SwiftSyntax 无法告知你任何关于代码的语义或类型信息。
与 SwiftSyntax 相反,一些如 SourceKit 之类的工具,操作的目标为更容易理解的 Swift 代码。这可以帮助此类 工具 实现一些编辑器相关的特性,例如代码补全或者文件之间的跳转。虽然 SwiftSyntax 不能像 SourceKit 一样实现跳转或者补全的功能,但在语法层面上也有很多应用场景,例如代码格式化和语法高亮。
揭秘 AST
抽象语法树在抽象层面上比较难以理解。因此我们先生成一个示例来一睹其貌。
留意一下如下的一行 Swift 代码,它声明了一个名为 one() 的函数,函数返回值为 1 :
func one() -> Int { return 1 }
在命令行中对此文件运行 swiftc 命令并传入 -frontend -emit-syntax 参数:
$ xcrun swiftc -frontend -emit-syntax ./One.swift
运行的结果为一串 JSON 格式的 AST。当你用 JSON 格式来展示时,AST 的结构会表现的更加清晰:
{
"kind": "SourceFile",
"layout": [{
"kind": "CodeBlockItemList",
"layout": [{
"kind": "CodeBlockItem",
"layout": [{
"kind": "FunctionDecl",
"layout": [null, null, {
"tokenKind": {
"kind": "kw_func"
},
"leadingTrivia": [],
"trailingTrivia": [{
"kind": "Space",
"value": 1
}],
"presence": "Present"
}, {
"tokenKind": {
"kind": "identifier",
"text": "one"
},
"leadingTrivia": [],
"trailingTrivia": [],
"presence": "Present"
}, ...
Python 中的 json.tool 模块提供了便捷地格式化 JSON 的能力。且几乎所有的 macOS 系统都已经集成了此模块,因此每个人都可以使用它。举个例子,你可以使用如下的命令对编译的输出结果使用 json.tool 格式化:
$ xcrun swiftc -frontend -emit-syntax ./One.swift | python -m json.tool
在最外层,可以看到 SourceFile ,它由 CodeBlockItemList 以及 CodeBlockItemList 内部的 CodeBlockItem 这几个部分组成。对于这个示例来说,仅有一个 CodeBlockItem 对应函数的定义( FunctionDecl ),其自身包含了几个子组件如函数签名、参数闭包和返回闭包。
术语 trivia 用于描述任何没有实际语法意义的东西,例如空格。每个标记符(Token)可以有一个或多个行前和行尾的 trivia。例如,在返回的闭包( -> Int )中的 Int 后的空格可以用如下的行尾 trivia 表示:
{
"kind": "Space",
"value": 1
}
处理文件系统限制
SwiftSyntax 通过代理系统的 swiftc 调用来生成抽象语法树。但是,这也限制了代码必须放在某个文件才能进行处理,而我们却经常需要对以字符串表示的代码进行处理。
为了解决这个限制,其中一种办法是把代码写入一个临时文件并传入到编译器中。
我们曾经尝试过写入临时文件 ,但目前,有更好的 API 可以帮助我们完成这项工作,它由 Swift Package Manager 本身提供。在你的 Package.swift 文件中,添加如下的包依赖关系,并把 Utility 依赖添加到正确的 target 中:
.package(url: "https://github.com/apple/swift-package-manager.git", from: "0.3.0"),
现在,你可以像下面这样引入 Basic 模块并使用 TemporaryFile API:
import Basic
import Foundation
let code: String
let tempfile = try TemporaryFile(deleteOnClose: true)
defer { tempfile.fileHandle.closeFile() }
tempfile.fileHandle.write(code.data(using: .utf8)!)
let url = URL(fileURLWithPath: tempfile.path.asString)
let sourceFile = try SyntaxTreeParser.parse(url)
我们可以用 SwiftSyntax 做什么
现在我们对 SwiftSyntax 如何工作已经有了足够的理解,是时候讨论一下几个使用它的方式了!
编写 Swift 代码:地狱模式
我们第一个想到,但却是最没有实际意义的 SwiftSyntax 用例就是让编写 Swift 代码的难度提升几个数量级。
利用 SwiftSyntax 中的 SyntaxFactory APIs,我们可以生成完整的 Swift 代码。不幸的是,编写这样的代码并不像闲庭散步般轻松。
留意一下如下的示例代码:
import SwiftSyntax
let structKeyword = SyntaxFactory.makeStructKeyword(trailingTrivia: .spaces(1))
let identifier = SyntaxFactory.makeIdentifier("Example", 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)
}
print(structureDeclaration)
唷。 那最后这段代码让我们得到了什么呢?
struct Example {
}
令人窒息的操作。
这绝不是为了取代 GYB 来用于每天的代码生成。(事实上, libSyntax 和 SwiftSyntax 都使用了 gyb 来生成接口。
但这个接口在某些特殊的问题上却格外有用。例如,你或许会使用 SwiftSyntax 来实现一个 Swift 编译器的 模糊测试 ,使用它可以随机生成一个表面有效却实际上非常复杂的程序,以此来进行压力测试。
重写 Swift 代码
在 SwiftSyntax 的 README 中有一个示例 展示了如何编写一个程序来遍历源文件中的整型并把他们的值加 1。
通过这个,你应该已经推断得出如何使用它来创建一个典型的 swift-format 工具。
但现在,我们先考虑一个相当 没有 效率——并且可能在万圣节(:jack_o_lantern:)这种需要捣蛋的场景才合适的用例,源代码重写:
import SwiftSyntax
public class ZalgoRewriter: SyntaxRewriter {
public override func visit(_ token: TokenSyntax) -> Syntax {
guard case let .stringLiteral(text) = token.tokenKind else {
return token
}
return token.withKind(.stringLiteral(zalgo(text)))
}
}
zalgo 函数是用来做什么的?可能不知道会更好……
不管怎样,在你的源代码中运行这个重写器,可以把所有的文本字符串转换为像下面一样的效果:
// Before :wave::smile:
print("Hello, world!")
// After :dizzy_face:
print("H͞͏̟̂ͩel̵ͬ͆͜ĺ͎̪̣͠ơ̡̼͓̋͝, w͎̽̇ͪ͢ǒ̩͔̲̕͝r̷̡̠͓̉͂l̘̳̆ͯ̊d!")
鬼魅一般,对吧?
高亮 Swift 代码
让我们用一个真正实用的东西来总结我们对 SwiftSyntax 的探究:一个 Swift 语法高亮工具。
从语法高亮工具的意义上来说,它可以把源代码按某种方式格式化为显示更为友好的 HTML。
NSHipster 通过 Jekyll 搭建 ,并使用了 Ruby 的库 Rouge 来渲染你在每篇文章中看到的示例代码。尽管如此,由于 Swift 的复杂语法和过快迭代,渲染出来的 HTML 并不是 100% 正确。
不同于 处理一堆麻烦的正则表达式 ,我们可以构造一个 语法高亮器 来放大 SwiftSyntax 对语言的理解的优势。
根据这个核心目的,实现的方法可以很直接:实现一个 SyntaxRewriter 的子类并重写 visit(_:) 方法,这个方法会在遍历源文件的每个标识符时被调用。通过判断每种不同的标识符类型,你可以把相应的可高亮标识符映射为 HTML 标记。
例如,数字文本可以用类名是 m 开头的 <span> 元素来表示( mf 表示浮点型, mi 表示整型)。如下是对应的在 SyntaxRewriter 子类中的代码:
import SwiftSyntax
class SwiftSyntaxHighlighter: SyntaxRewriter {
var html: String = ""
override func visit(_ token: TokenSyntax) -> Syntax {
switch token.tokenKind {
// ...
case .floatingLiteral(let string):
html += "<span class=\"mf\">\(string)</span>"
case .integerLiteral(let string):
if string.hasPrefix("0b") {
html += "<span class=\"mb\">\(string)</span>"
} else if string.hasPrefix("0o") {
html += "<span class=\"mo\">\(string)</span>"
} else if string.hasPrefix("0x") {
html += "<span class=\"mh\">\(string)</span>"
} else {
html += "<span class=\"mi\">\(string)</span>"
}
// ...
default:
break
}
return token
}
}
尽管 SyntaxRewritere 针对每一种不同类型的语法元素,都已经实现了 visit(:) 方法,但我发现使用一个 switch 语句可以更简单地处理所有工作。(在 default 分支中打印出无法处理的标记符,可以更好地帮助我们找到那些没有处理的情况)。这不是最优雅的实现,但鉴于我对 SwiftSyntax 不足的理解,这是个较好的开端。
不管怎样,在几个小时的开发工作后,我已经可以在 Swift 大量的语法特性中,生成出比较理想的渲染过的输出。
这个项目需要一个库和命令行工具的支持。快去 尝试一下 然后让我知道你的想法吧!
本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权,最新文章请访问http://swift.gg。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Automate This
Christopher Steiner / Portfolio / 2013-8-9 / USD 25.95
"The rousing story of the last gasp of human agency and how today's best and brightest minds are endeavoring to put an end to it." It used to be that to diagnose an illness, interpret legal docume......一起来看看 《Automate This》 这本书的介绍吧!