一个 A prettier printer (Wadler 2003) 的 Swift 实现,移植自 wl-pprint-annotated
漂亮打印机是解析器的对偶 -- 接收任意 AST 并将其输出为字符串。 这个库是一系列组合器和一个名为 Doc
的原语的集合,用于描述漂亮地打印某些数据,就像解析器组合器库提供组合器和原语来描述将测试解析为 AST 一样。有趣的是,此实现有效地找到最佳漂亮打印。 您可以使用各种 Doc
组合器来编码您对最佳的理解。
例如:假设我们有一些 Swift 代码的内部结构化表示
func aLongFunction(foo: String, bar: Int, baz: Long) -> (String, Int, Long) {
sideEffect()
return (foo, bar, baz)
}
使用此库,描述以 120 个字符的页面宽度漂亮地打印上述代码。也打印
在 40 个字符的页面宽度下
func aLongFunction(
foo: String, bar: Int, baz: Long
) -> (String, Int, Long) {
sideEffect()
return (foo, bar, baz)
}
在 20 个字符的页面宽度下
func aLongFunction(
foo: String,
bar: Int,
baz: Long
) -> (
String,
Int,
Long
) {
sideEffect()
return (
foo,
bar,
baz
)
}
请参阅 testSwiftExample
测试用例中此特定文档的编码。
如果您要输出文本并且关心页面的宽度。 序列化到 Doc
可以让您一次性捕获您的换行逻辑以及您的输出字符串的外观。
您为什么要输出文本并关心页面宽度?
gofmt
类型的工具(注意:gofmt
不会根据宽度进行漂亮打印,refmt
(Reason) 和 prettier
(JavaScript) 会)一个 A prettier printer (Wadler 2003) 论文的 Swift 实现 (包括通常接受的现代增强功能 ala wl-pprint-annotated。 此实现接近于 wl-pprint-annotated 的直接移植,并受到 scala-optparse-applicative's Doc 和一些额外的 Swift 风格的影响。
Doc
非常可组合。 首先,它是一个带有 .empty
文档和 .concat
情况(仅将两个文档彼此相邻放置)的 monoid
。 我们还有一个名为 grouped
的原语,它尝试在单行上显示此文档,但如果它不适合,则将其分解为新行。 从那里我们构建所有高级组合器。
x <%> y
在 x 和 y 之间用空格连接,如果它适合,否则放一行。
.text("foo") <%> .text("bar")
在较大的页面宽度下漂亮地打印
foo bar
但是当页面宽度设置为 5
时打印
foo
bar
这里有更多组合器
/// Concats x and y with a space in between
static func <+>(x: Doc, y: Doc) -> Doc
/// Behaves like `space` if the output fits the page
/// Otherwise it behaves like line
static var softline
/// Concats x and y together if it fits
/// Otherwise puts a line in between
static func <%%>(x: Doc, y: Doc) -> Doc
/// Behaves like `zero` if the output fits the page
/// Otherwise it behaves like line
static var softbreak: Doc
/// Puts a line between x and y that can be flattened to a space
static func <&>(x: Doc, y: Doc) -> Doc
/// Puts a line between x and y that can be flattened with no space
static func <&&>(x: Doc, y: Doc) -> Doc
还有一些组合器可以将文档集合转换为“集合”之类的漂亮打印原语,例如方括号分隔的列表
.text("let x =") <%> [ "foo", "bar", "baz" ].map(Doc.text).list(indent: 4)
以页面宽度 80 漂亮打印为
let x = [foo, bar, baz]
在页面宽度 10 下为
let x = [
foo,
bar,
baz
]
请参阅源代码以获取更多文档,我已包含描述性文档注释以解释所有运算符(主要来自 wl-pprint-annotated)。
Doc
目前有两种渲染方法:renderPrettyDefault
以 100 的页面宽度打印,renderPretty
允许您控制页面宽度。
这些方法不直接返回 String
-- 它们返回 SimpleDoc
,这是一个接近于字符串的低级 IR,但足够高,您可以有效地输出到一些其他输出系统,如 stdout 或文件。
目前,SimpleDoc
有 displayString()
,它输出一个 String
,并且
func display<M: Monoid>(readString: (String) -> M) -> M
display
接受一个可以将字符串转换为 monoid 的函数,然后将所有内容组合在一起。 因为这适用于任何 monoid,所以您只需要为您的输出格式化程序提供一个 monoid 实例(写入 stdout 或文件)。
使用 Swift Package Manager,将其放入您的 Package.swift
中
.Package(url: "https://github.com/bkase/DoctorPretty.git",
majorVersion: 0, minor: 5)
阅读 A prettier printer (Wadler 2003) 论文。
Doc
是一个递归枚举,它捕获文本和新行。 有趣的情况是 .union(longerLines: Doc, shorterLines: Doc)
。 这种情况具体化了“首先尝试较长的行,然后尝试较短的行”的概念。 我们可以构建各种以不同方式组合 Doc
的高级组合器,最终减少为一些战略性的 .union
。
渲染器保留一个工作列表,每个规则从列表中删除或添加工作项,并递归直到列表为空。 目前,最佳拟合度量标准贪婪地进行,但将来可以轻松地换成更智能的算法。