Doctor Pretty

一个 A prettier printer (Wadler 2003) 的 Swift 实现,移植自 wl-pprint-annotated

Build Status

这是什么

漂亮打印机是解析器的对偶 -- 接收任意 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 可以让您一次性捕获您的换行逻辑以及您的输出字符串的外观。

您为什么要输出文本并关心页面宽度?

  1. 您正在为 Swift 构建一个 gofmt 类型的工具(注意:gofmt 不会根据宽度进行漂亮打印,refmt (Reason) 和 prettier (JavaScript) 会)
  2. 您正在编写某种代码生成工具来输出 Swift 代码
  3. 您正在构建源到源的转译器
  4. 您正在终端窗口中为某些命令行应用程序输出帮助消息(我计划将此用于 https://github.com/bkase/swift-optparse-applicative

这实际上是什么

一个 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 或文件。

目前,SimpleDocdisplayString(),它输出一个 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

渲染器保留一个工作列表,每个规则从列表中删除或添加工作项,并递归直到列表为空。 目前,最佳拟合度量标准贪婪地进行,但将来可以轻松地换成更智能的算法。