跳到主要内容

prettyprinter:使用函数组合解决结构化数据打印问题

· 阅读需 9 分钟

结构化数据的打印是编程中常见的问题,尤其是在调试和日志记录时。如何展示复杂的数据结构,并能够根据屏幕宽度调整排版?例如,对于一个数组字面量 [a,b,c] , 我们希望在屏幕宽度足够时打印为一行,而在屏幕宽度不足时自动换行并缩进。 传统的解决方案往往依赖于手动处理字符串拼接和维护缩进状态,这样的方式不仅繁琐,而且容易出错。

本篇文章将介绍一种基于函数组合的实用方案——prettyprinter的实现。Prettyprinter 向用户提供了一系列函数, 这些函数能够组合成一个描述了打印方式的Doc原语。然后,根据宽度配置和Doc原语生成最终的字符串。函数组合的思路使得用户能够复用已有的代码,声明式地实现数据结构的打印。

SimpleDoc 原语

我们先定义一个SimpleDoc表示4个最简单的原语,来处理最基本的字符串拼接和换行。

enum SimpleDoc {
  
SimpleDoc
Empty
SimpleDoc
Line
(String) -> SimpleDoc
Text
(
String
String
)
(SimpleDoc, SimpleDoc) -> SimpleDoc
Cat
(
enum SimpleDoc {
  Empty
  Line
  Text(String)
  Cat(SimpleDoc, SimpleDoc)
}
SimpleDoc
,
enum SimpleDoc {
  Empty
  Line
  Text(String)
  Cat(SimpleDoc, SimpleDoc)
}
SimpleDoc
)
}
  • Empty: 表示空字符串
  • Line:表示换行
  • Text(String): 表示一个不包含换行的文本片段
  • Cat(SimpleDoc, SimpleDoc): 按顺序组合两个 SimpleDoc

按照上面每个原语的定义,我们可以实现一个简单的渲染函数:这个函数使用一个栈来保存待处理的SimpleDoc,逐个将它们转换为字符串。

fn 
enum SimpleDoc {
  Empty
  Line
  Text(String)
  Cat(SimpleDoc, SimpleDoc)
}
SimpleDoc
::
(doc : SimpleDoc) -> String
render
(
SimpleDoc
doc
:
enum SimpleDoc {
  Empty
  Line
  Text(String)
  Cat(SimpleDoc, SimpleDoc)
}
SimpleDoc
) ->
String
String
{
let
StringBuilder
buf
=
type StringBuilder
StringBuilder
::
(size_hint? : Int) -> StringBuilder

Creates a new string builder with an optional initial capacity hint.

Parameters:

  • size_hint : An optional initial capacity hint for the internal buffer. If less than 1, a minimum capacity of 1 is used. Defaults to 0. It is the size of bytes, not the size of characters. size_hint may be ignored on some platforms, JS for example.

Returns a new StringBuilder instance with the specified initial capacity.

new
()
let
Array[SimpleDoc]
stack
= [
SimpleDoc
doc
]
while
Array[SimpleDoc]
stack
.
(self : Array[SimpleDoc]) -> SimpleDoc?

Removes the last element from a array and returns it, or None if it is empty.

Example

  let v = [1, 2, 3]
  assert_eq(v.pop(), Some(3))
  assert_eq(v, [1, 2])
pop
() is
(SimpleDoc) -> SimpleDoc?
Some
(
SimpleDoc
doc
) {
match
SimpleDoc
doc
{
SimpleDoc
Empty
=> ()
SimpleDoc
Line
=> {
StringBuilder
buf
..
(self : StringBuilder, str : String) -> Unit

Writes a string to the StringBuilder.

write_string
("\n")
}
(String) -> SimpleDoc
Text
(
String
text
) => {
StringBuilder
buf
.
(self : StringBuilder, str : String) -> Unit

Writes a string to the StringBuilder.

write_string
(
String
text
)
}
(SimpleDoc, SimpleDoc) -> SimpleDoc
Cat
(
SimpleDoc
left
,
SimpleDoc
right
) =>
Array[SimpleDoc]
stack
..
(self : Array[SimpleDoc], value : SimpleDoc) -> Unit

Adds an element to the end of the array.

If the array is at capacity, it will be reallocated.

Example

  let v = []
  v.push(3)
push
(
SimpleDoc
right
)..
(self : Array[SimpleDoc], value : SimpleDoc) -> Unit

Adds an element to the end of the array.

If the array is at capacity, it will be reallocated.

Example

  let v = []
  v.push(3)
push
(
SimpleDoc
left
)
} }
StringBuilder
buf
.
(self : StringBuilder) -> String

Returns the current content of the StringBuilder as a string.

to_string
()
}

编写测试,可以看到SimpleDoc的表达能力和 String 相当: Empty 相当于 ""Line 相当于 "\n" , Text("a") 相当于 "a"Cat(Text("a"), Text("b")) 相当于 "a" + "b"

test "simple doc" {
  let 
SimpleDoc
doc
:
enum SimpleDoc {
  Empty
  Line
  Text(String)
  Cat(SimpleDoc, SimpleDoc)
}
SimpleDoc
=
(SimpleDoc, SimpleDoc) -> SimpleDoc
Cat
(
(String) -> SimpleDoc
Text
("hello"),
(SimpleDoc, SimpleDoc) -> SimpleDoc
Cat
(
SimpleDoc
Line
,
(String) -> SimpleDoc
Text
("world")))
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError

Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of Show implementations and program outputs.

Parameters:

  • object : The object to be inspected. Must implement the Show trait.
  • content : The expected string representation of the object. Defaults to an empty string.
  • location : Source code location information for error reporting. Automatically provided by the compiler.
  • arguments_location : Location information for function arguments in source code. Automatically provided by the compiler.

Throws an InspectError if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.

Example:

  inspect(42, content="42")
  inspect("hello", content="hello")
  inspect([1, 2, 3], content="[1, 2, 3]")
inspect
(
SimpleDoc
doc
.
(doc : SimpleDoc) -> String
render
(),
String
content
=(
#|hello #|world ), ) }

目前它还和String一样无法方便地处理缩进和排版切换。不过,只要再添加三个原语就可以解决这些问题。

ExtendDoc:Nest, Choice, Group

接下来我们在SimpleDoc的基础上,添加三个新的原语Nest、Choice、Group来处理更复杂的打印需求。

enum ExtendDoc {
  
ExtendDoc
Empty
ExtendDoc
Line
(String) -> ExtendDoc
Text
(
String
String
)
(ExtendDoc, ExtendDoc) -> ExtendDoc
Cat
(
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
,
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
)
(Int, ExtendDoc) -> ExtendDoc
Nest
(
Int
Int
,
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
)
(ExtendDoc, ExtendDoc) -> ExtendDoc
Choice
(
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
,
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
)
(ExtendDoc) -> ExtendDoc
Group
(
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
)
}
  • Nest Nest(Int, ExtendDoc) 用于处理缩进。第一个参数表示缩进的空格数,第二个参数表示内部的 ExtendDoc 。当内部的 ExtendDoc 包含 Line 时,render函数将在打印换行的同时追加相应数量的空格。 Nest 嵌套使用时缩进会累加。

  • Choice Choice(ExtendDoc, ExtendDoc) 保存了两种打印方式。通常第一个参数表示不包含换行更紧凑的布局,第二个参数则是包含 Line 的布局。当render在紧凑模式时,使用第一个布局,否则使用第二个。

  • Group Group(ExtendDoc) 将ExtendDoc分组,并根据 ExtendDoc 的长度和剩余的空间切换打印 ExtendDoc 时的模式。如果剩余空间足够,则在紧凑模式下打印,否则使用包含换行的布局。

计算所需空间

Group的实现需要计算 ExtendDoc 的空间需求,以便决定是否使用紧凑模式。我们可以为 ExtendDoc 添加一个 space() 方法来计算每个布局片段所需的空间。

let 
Int
max_space
= 9999
fn
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
::
(self : ExtendDoc) -> Int
space
(
ExtendDoc
self
:
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
Self
) ->
Int
Int
{
match
ExtendDoc
self
{
ExtendDoc
Empty
=> 0
ExtendDoc
Line
=>
Int
max_space
(String) -> ExtendDoc
Text
(
String
str
) =>
String
str
.
(self : String) -> Int

Returns the number of UTF-16 code units in the string. Note that this is not necessarily equal to the number of Unicode characters (code points) in the string, as some characters may be represented by multiple UTF-16 code units.

Parameters:

  • string : The string whose length is to be determined.

Returns the number of UTF-16 code units in the string.

Example:

  inspect("hello".length(), content="5")
  inspect("🤣".length(), content="2") // Emoji uses two UTF-16 code units
  inspect("".length(), content="0") // Empty string
length
()
(ExtendDoc, ExtendDoc) -> ExtendDoc
Cat
(
ExtendDoc
a
,
ExtendDoc
b
) =>
ExtendDoc
a
.
(self : ExtendDoc) -> Int
space
()
(self : Int, other : Int) -> Int

Adds two 32-bit signed integers. Performs two's complement arithmetic, which means the operation will wrap around if the result exceeds the range of a 32-bit integer.

Parameters:

  • self : The first integer operand.
  • other : The second integer operand.

Returns a new integer that is the sum of the two operands. If the mathematical sum exceeds the range of a 32-bit integer (-2,147,483,648 to 2,147,483,647), the result wraps around according to two's complement rules.

Example:

  inspect(42 + 1, content="43")
  inspect(2147483647 + 1, content="-2147483648") // Overflow wraps around to minimum value
+
ExtendDoc
b
.
(self : ExtendDoc) -> Int
space
()
(Int, ExtendDoc) -> ExtendDoc
Nest
(_,
ExtendDoc
a
) |
(ExtendDoc, ExtendDoc) -> ExtendDoc
Choice
(
ExtendDoc
a
, _) |
(ExtendDoc) -> ExtendDoc
Group
(
ExtendDoc
a
) =>
ExtendDoc
a
.
(self : ExtendDoc) -> Int
space
()
} }

对于 Line , 我们假设它总是需要占用无限大的空间。这样如果 Group 内包含 Line,能够保证render处理内部的 ExtendDoc 时不会进入紧凑模式。

实现 ExtendDoc::render

我们在SimpleDoc::render的基础上实现 ExtendDoc::render 。 render在打印完一个子结构后,继续打印后续的结构需要退回到原先的缩进层级,因此需要在stack中额外保存每个待打印的ExtendDoc的两个状态:缩进和是否在紧凑模式。我们还需要维护了一个在render过程中更新的 column 变量,表示当前行的已经使用的字符数,以计算当前行所剩的空间。另外,函数增加了额外的width参数,表示每行的最大宽度限制。

fn 
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
::
(doc : ExtendDoc, width? : Int) -> String
render
(
ExtendDoc
doc
:
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
,
Int
width
~ :
Int
Int
= 80) ->
String
String
{
let
StringBuilder
buf
=
type StringBuilder
StringBuilder
::
(size_hint? : Int) -> StringBuilder

Creates a new string builder with an optional initial capacity hint.

Parameters:

  • size_hint : An optional initial capacity hint for the internal buffer. If less than 1, a minimum capacity of 1 is used. Defaults to 0. It is the size of bytes, not the size of characters. size_hint may be ignored on some platforms, JS for example.

Returns a new StringBuilder instance with the specified initial capacity.

new
()
let
Array[(Int, Bool, ExtendDoc)]
stack
= [(0, false,
ExtendDoc
doc
)] // 默认不缩进,非紧凑模式
let mut
Int
column
= 0
while
Array[(Int, Bool, ExtendDoc)]
stack
.
(self : Array[(Int, Bool, ExtendDoc)]) -> (Int, Bool, ExtendDoc)?

Removes the last element from a array and returns it, or None if it is empty.

Example

  let v = [1, 2, 3]
  assert_eq(v.pop(), Some(3))
  assert_eq(v, [1, 2])
pop
() is
((Int, Bool, ExtendDoc)) -> (Int, Bool, ExtendDoc)?
Some
((
Int
indent
,
Bool
fit
,
ExtendDoc
doc
)) {
match
ExtendDoc
doc
{
ExtendDoc
Empty
=> ()
ExtendDoc
Line
=> {
StringBuilder
buf
..
(self : StringBuilder, str : String) -> Unit

Writes a string to the StringBuilder.

write_string
("\n")
// 在换行后打印需要的缩进 for _ in
Int
0
..<
Int
indent
{
StringBuilder
buf
.
(self : StringBuilder, str : String) -> Unit

Writes a string to the StringBuilder.

write_string
(" ")
} // 重置当前行的字符数
Int
column
=
Int
indent
}
(String) -> ExtendDoc
Text
(
String
text
) => {
StringBuilder
buf
.
(self : StringBuilder, str : String) -> Unit

Writes a string to the StringBuilder.

write_string
(
String
text
)
// 更新当前行的字符数
Int
column
(self : Int, other : Int) -> Int

Adds two 32-bit signed integers. Performs two's complement arithmetic, which means the operation will wrap around if the result exceeds the range of a 32-bit integer.

Parameters:

  • self : The first integer operand.
  • other : The second integer operand.

Returns a new integer that is the sum of the two operands. If the mathematical sum exceeds the range of a 32-bit integer (-2,147,483,648 to 2,147,483,647), the result wraps around according to two's complement rules.

Example:

  inspect(42 + 1, content="43")
  inspect(2147483647 + 1, content="-2147483648") // Overflow wraps around to minimum value
+=
String
text
.
(self : String) -> Int

Returns the number of UTF-16 code units in the string. Note that this is not necessarily equal to the number of Unicode characters (code points) in the string, as some characters may be represented by multiple UTF-16 code units.

Parameters:

  • string : The string whose length is to be determined.

Returns the number of UTF-16 code units in the string.

Example:

  inspect("hello".length(), content="5")
  inspect("🤣".length(), content="2") // Emoji uses two UTF-16 code units
  inspect("".length(), content="0") // Empty string
length
()
}
(ExtendDoc, ExtendDoc) -> ExtendDoc
Cat
(
ExtendDoc
left
,
ExtendDoc
right
) =>
Array[(Int, Bool, ExtendDoc)]
stack
..
(self : Array[(Int, Bool, ExtendDoc)], value : (Int, Bool, ExtendDoc)) -> Unit

Adds an element to the end of the array.

If the array is at capacity, it will be reallocated.

Example

  let v = []
  v.push(3)
push
((
Int
indent
,
Bool
fit
,
ExtendDoc
right
))..
(self : Array[(Int, Bool, ExtendDoc)], value : (Int, Bool, ExtendDoc)) -> Unit

Adds an element to the end of the array.

If the array is at capacity, it will be reallocated.

Example

  let v = []
  v.push(3)
push
((
Int
indent
,
Bool
fit
,
ExtendDoc
left
))
(Int, ExtendDoc) -> ExtendDoc
Nest
(
Int
n
,
ExtendDoc
doc
) =>
Array[(Int, Bool, ExtendDoc)]
stack
..
(self : Array[(Int, Bool, ExtendDoc)], value : (Int, Bool, ExtendDoc)) -> Unit

Adds an element to the end of the array.

If the array is at capacity, it will be reallocated.

Example

  let v = []
  v.push(3)
push
((
Int
indent
(self : Int, other : Int) -> Int

Adds two 32-bit signed integers. Performs two's complement arithmetic, which means the operation will wrap around if the result exceeds the range of a 32-bit integer.

Parameters:

  • self : The first integer operand.
  • other : The second integer operand.

Returns a new integer that is the sum of the two operands. If the mathematical sum exceeds the range of a 32-bit integer (-2,147,483,648 to 2,147,483,647), the result wraps around according to two's complement rules.

Example:

  inspect(42 + 1, content="43")
  inspect(2147483647 + 1, content="-2147483648") // Overflow wraps around to minimum value
+
Int
n
,
Bool
fit
,
ExtendDoc
doc
)) // 增加缩进
(ExtendDoc, ExtendDoc) -> ExtendDoc
Choice
(
ExtendDoc
a
,
ExtendDoc
b
) =>
Array[(Int, Bool, ExtendDoc)]
stack
.
(self : Array[(Int, Bool, ExtendDoc)], value : (Int, Bool, ExtendDoc)) -> Unit

Adds an element to the end of the array.

If the array is at capacity, it will be reallocated.

Example

  let v = []
  v.push(3)
push
(if
Bool
fit
{ (
Int
indent
,
Bool
fit
,
ExtendDoc
a
) } else { (
Int
indent
,
Bool
fit
,
ExtendDoc
b
) })
(ExtendDoc) -> ExtendDoc
Group
(
ExtendDoc
doc
) => {
// 如果已经在紧凑模式下,直接使用紧凑布局。如果不在紧凑模式下,但是要打印的内容可以放入当前行,则进入紧凑模式。 let
Bool
fit
=
Bool
fit
(Bool, Bool) -> Bool
||
Int
column
(self : Int, other : Int) -> Int

Adds two 32-bit signed integers. Performs two's complement arithmetic, which means the operation will wrap around if the result exceeds the range of a 32-bit integer.

Parameters:

  • self : The first integer operand.
  • other : The second integer operand.

Returns a new integer that is the sum of the two operands. If the mathematical sum exceeds the range of a 32-bit integer (-2,147,483,648 to 2,147,483,647), the result wraps around according to two's complement rules.

Example:

  inspect(42 + 1, content="43")
  inspect(2147483647 + 1, content="-2147483648") // Overflow wraps around to minimum value
+
ExtendDoc
doc
.
(self : ExtendDoc) -> Int
space
()
(self_ : Int, other : Int) -> Bool
<=
Int
width
Array[(Int, Bool, ExtendDoc)]
stack
.
(self : Array[(Int, Bool, ExtendDoc)], value : (Int, Bool, ExtendDoc)) -> Unit

Adds an element to the end of the array.

If the array is at capacity, it will be reallocated.

Example

  let v = []
  v.push(3)
push
((
Int
indent
,
Bool
fit
,
ExtendDoc
doc
))
} } }
StringBuilder
buf
.
(self : StringBuilder) -> String

Returns the current content of the StringBuilder as a string.

to_string
()
}

下面我们尝试用 ExtendDoc 描述一个 (expr) ,并在不同的宽度配置下打印它:

let 
ExtendDoc
softline
:
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
=
(ExtendDoc, ExtendDoc) -> ExtendDoc
Choice
(
ExtendDoc
Empty
,
ExtendDoc
Line
)
impl
trait Add {
  add(Self, Self) -> Self
  op_add(Self, Self) -> Self
}

types implementing this trait can use the + operator

Add
for
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
with
(a : ExtendDoc, b : ExtendDoc) -> ExtendDoc
op_add
(
ExtendDoc
a
,
ExtendDoc
b
) {
(ExtendDoc, ExtendDoc) -> ExtendDoc
Cat
(
ExtendDoc
a
,
ExtendDoc
b
)
} test "tuple" { let
ExtendDoc
tuple
:
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
=
(ExtendDoc) -> ExtendDoc
Group
(
(String) -> ExtendDoc
Text
("(")
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
(Int, ExtendDoc) -> ExtendDoc
Nest
(2,
ExtendDoc
softline
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
(String) -> ExtendDoc
Text
("expr"))
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
ExtendDoc
softline
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
(String) -> ExtendDoc
Text
(")"),
)
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError

Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of Show implementations and program outputs.

Parameters:

  • object : The object to be inspected. Must implement the Show trait.
  • content : The expected string representation of the object. Defaults to an empty string.
  • location : Source code location information for error reporting. Automatically provided by the compiler.
  • arguments_location : Location information for function arguments in source code. Automatically provided by the compiler.

Throws an InspectError if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.

Example:

  inspect(42, content="42")
  inspect("hello", content="hello")
  inspect([1, 2, 3], content="[1, 2, 3]")
inspect
(
ExtendDoc
tuple
.
(doc : ExtendDoc, width~ : Int) -> String
render
(
Int
width
=40),
String
content
="(expr)")
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError

Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of Show implementations and program outputs.

Parameters:

  • object : The object to be inspected. Must implement the Show trait.
  • content : The expected string representation of the object. Defaults to an empty string.
  • location : Source code location information for error reporting. Automatically provided by the compiler.
  • arguments_location : Location information for function arguments in source code. Automatically provided by the compiler.

Throws an InspectError if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.

Example:

  inspect(42, content="42")
  inspect("hello", content="hello")
  inspect([1, 2, 3], content="[1, 2, 3]")
inspect
(
ExtendDoc
tuple
.
(doc : ExtendDoc, width~ : Int) -> String
render
(
Int
width
=5),
String
content
=(
#|( #| expr #|) ), ) }

我们先通过组合EmptyLine的方式定义了一个在紧凑模式下不换行的 softline 。render默认以非紧凑模式开始打印,所以我们需要用 Group 将整个表达式包裹起来。这样在宽度足够时,整个表达式会打印为一行,而在宽度不足时会自动换行并缩进。为了减少嵌套的括号,改善可读性,这里给 ExtendDoc 重载了 + 运算符。

组合函数

在prettyprinter的实践中,用户更多地会使用在 ExtendDoc 原语基础之上组合出的函数——例如之前使用过的 softline 。下面将介绍一些实用的函数,帮助我们解决结构化打印的问题。

softline & softbreak

let 
ExtendDoc
softbreak
:
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
=
(ExtendDoc, ExtendDoc) -> ExtendDoc
Choice
(
(String) -> ExtendDoc
Text
(" "),
ExtendDoc
Line
)

softline 类似,不同的是在紧凑模式下它会加入额外的空格。注意在同一层 Group 中,每个 Choice 都会一致选择紧凑或非紧凑模式。

let 
ExtendDoc
abc
:
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
=
(String) -> ExtendDoc
Text
("abc")
let
ExtendDoc
def
:
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
=
(String) -> ExtendDoc
Text
("def")
let
ExtendDoc
ghi
:
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
=
(String) -> ExtendDoc
Text
("ghi")
test "softbreak" { let
ExtendDoc
doc
:
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
=
(ExtendDoc) -> ExtendDoc
Group
(
ExtendDoc
abc
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
ExtendDoc
softbreak
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
ExtendDoc
def
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
ExtendDoc
softbreak
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
ExtendDoc
ghi
)
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError

Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of Show implementations and program outputs.

Parameters:

  • object : The object to be inspected. Must implement the Show trait.
  • content : The expected string representation of the object. Defaults to an empty string.
  • location : Source code location information for error reporting. Automatically provided by the compiler.
  • arguments_location : Location information for function arguments in source code. Automatically provided by the compiler.

Throws an InspectError if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.

Example:

  inspect(42, content="42")
  inspect("hello", content="hello")
  inspect([1, 2, 3], content="[1, 2, 3]")
inspect
(
ExtendDoc
doc
.
(doc : ExtendDoc, width~ : Int) -> String
render
(
Int
width
=20),
String
content
="abc def ghi")
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError

Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of Show implementations and program outputs.

Parameters:

  • object : The object to be inspected. Must implement the Show trait.
  • content : The expected string representation of the object. Defaults to an empty string.
  • location : Source code location information for error reporting. Automatically provided by the compiler.
  • arguments_location : Location information for function arguments in source code. Automatically provided by the compiler.

Throws an InspectError if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.

Example:

  inspect(42, content="42")
  inspect("hello", content="hello")
  inspect([1, 2, 3], content="[1, 2, 3]")
inspect
(
ExtendDoc
doc
.
(doc : ExtendDoc, width~ : Int) -> String
render
(
Int
width
=10),
String
content
=(
#|abc #|def #|ghi ), ) }

autoline & autobreak

let 
ExtendDoc
autoline
:
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
=
(ExtendDoc) -> ExtendDoc
Group
(
ExtendDoc
softline
)
let
ExtendDoc
autobreak
:
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
=
(ExtendDoc) -> ExtendDoc
Group
(
ExtendDoc
softbreak
)

autolineautobreak 实现一种类似于文字编辑器的排版:尽可能多地将内容放进一行内,溢出则换行。

test {
  let 
ExtendDoc
doc
:
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
=
(ExtendDoc) -> ExtendDoc
Group
(
ExtendDoc
abc
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
ExtendDoc
autobreak
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
ExtendDoc
def
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
ExtendDoc
autobreak
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
ExtendDoc
ghi
,
)
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError

Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of Show implementations and program outputs.

Parameters:

  • object : The object to be inspected. Must implement the Show trait.
  • content : The expected string representation of the object. Defaults to an empty string.
  • location : Source code location information for error reporting. Automatically provided by the compiler.
  • arguments_location : Location information for function arguments in source code. Automatically provided by the compiler.

Throws an InspectError if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.

Example:

  inspect(42, content="42")
  inspect("hello", content="hello")
  inspect([1, 2, 3], content="[1, 2, 3]")
inspect
(
ExtendDoc
doc
.
(doc : ExtendDoc, width~ : Int) -> String
render
(
Int
width
=10),
String
content
="abc def ghi")
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError

Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of Show implementations and program outputs.

Parameters:

  • object : The object to be inspected. Must implement the Show trait.
  • content : The expected string representation of the object. Defaults to an empty string.
  • location : Source code location information for error reporting. Automatically provided by the compiler.
  • arguments_location : Location information for function arguments in source code. Automatically provided by the compiler.

Throws an InspectError if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.

Example:

  inspect(42, content="42")
  inspect("hello", content="hello")
  inspect([1, 2, 3], content="[1, 2, 3]")
inspect
(
ExtendDoc
doc
.
(doc : ExtendDoc, width~ : Int) -> String
render
(
Int
width
=5),
String
content
=(
#|abc def #|ghi ), )
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError

Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of Show implementations and program outputs.

Parameters:

  • object : The object to be inspected. Must implement the Show trait.
  • content : The expected string representation of the object. Defaults to an empty string.
  • location : Source code location information for error reporting. Automatically provided by the compiler.
  • arguments_location : Location information for function arguments in source code. Automatically provided by the compiler.

Throws an InspectError if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.

Example:

  inspect(42, content="42")
  inspect("hello", content="hello")
  inspect([1, 2, 3], content="[1, 2, 3]")
inspect
(
ExtendDoc
doc
.
(doc : ExtendDoc, width~ : Int) -> String
render
(
Int
width
=3),
String
content
=(
#|abc #|def #|ghi ), ) }

sepby

fn 
(xs : Array[ExtendDoc], sep : ExtendDoc) -> ExtendDoc
sepby
(
Array[ExtendDoc]
xs
:
type Array[T]

An Array is a collection of values that supports random access and can grow in size.

Array
[
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
],
ExtendDoc
sep
:
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
) ->
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
{
match
Array[ExtendDoc]
xs
{
[] =>
ExtendDoc
Empty
Array[ExtendDoc]
[
ExtendDoc
x
Array[ExtendDoc]
, .. xs]
=>
ArrayView[ExtendDoc]
xs
.
(self : ArrayView[ExtendDoc], init~ : ExtendDoc, f : (ExtendDoc, ExtendDoc) -> ExtendDoc) -> ExtendDoc

Fold out values from an View according to certain rules.

Example

  let sum = [1, 2, 3, 4, 5][:].fold(init=0, (sum, elem) => sum + elem)
  inspect(sum, content="15")
fold
(
ExtendDoc
init
=
ExtendDoc
x
, (
ExtendDoc
a
,
ExtendDoc
b
) =>
ExtendDoc
a
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
ExtendDoc
sep
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
ExtendDoc
b
)
} }

sepby会在ExtendDoc之间插入分隔符sep

let 
ExtendDoc
comma
:
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
=
(String) -> ExtendDoc
Text
(",")
test { let
ExtendDoc
layout
=
(ExtendDoc) -> ExtendDoc
Group
(
(xs : Array[ExtendDoc], sep : ExtendDoc) -> ExtendDoc
sepby
([
ExtendDoc
abc
,
ExtendDoc
def
,
ExtendDoc
ghi
],
ExtendDoc
comma
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
ExtendDoc
softbreak
))
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError

Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of Show implementations and program outputs.

Parameters:

  • object : The object to be inspected. Must implement the Show trait.
  • content : The expected string representation of the object. Defaults to an empty string.
  • location : Source code location information for error reporting. Automatically provided by the compiler.
  • arguments_location : Location information for function arguments in source code. Automatically provided by the compiler.

Throws an InspectError if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.

Example:

  inspect(42, content="42")
  inspect("hello", content="hello")
  inspect([1, 2, 3], content="[1, 2, 3]")
inspect
(
ExtendDoc
layout
.
(doc : ExtendDoc, width~ : Int) -> String
render
(
Int
width
=40),
String
content
="abc, def, ghi")
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError

Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of Show implementations and program outputs.

Parameters:

  • object : The object to be inspected. Must implement the Show trait.
  • content : The expected string representation of the object. Defaults to an empty string.
  • location : Source code location information for error reporting. Automatically provided by the compiler.
  • arguments_location : Location information for function arguments in source code. Automatically provided by the compiler.

Throws an InspectError if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.

Example:

  inspect(42, content="42")
  inspect("hello", content="hello")
  inspect([1, 2, 3], content="[1, 2, 3]")
inspect
(
ExtendDoc
layout
.
(doc : ExtendDoc, width~ : Int) -> String
render
(
Int
width
=10),
String
content
=(
#|abc, #|def, #|ghi ), ) }

surround

fn 
(m : ExtendDoc, l : ExtendDoc, r : ExtendDoc) -> ExtendDoc
surround
(
ExtendDoc
m
:
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
,
ExtendDoc
l
:
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
,
ExtendDoc
r
:
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
) ->
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
{
ExtendDoc
l
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
ExtendDoc
m
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
ExtendDoc
r
}

surround 用于在 ExtendDoc 的两侧添加括号或其他分隔符。

test {
  
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError

Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of Show implementations and program outputs.

Parameters:

  • object : The object to be inspected. Must implement the Show trait.
  • content : The expected string representation of the object. Defaults to an empty string.
  • location : Source code location information for error reporting. Automatically provided by the compiler.
  • arguments_location : Location information for function arguments in source code. Automatically provided by the compiler.

Throws an InspectError if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.

Example:

  inspect(42, content="42")
  inspect("hello", content="hello")
  inspect([1, 2, 3], content="[1, 2, 3]")
inspect
(
(m : ExtendDoc, l : ExtendDoc, r : ExtendDoc) -> ExtendDoc
surround
(
ExtendDoc
abc
,
(String) -> ExtendDoc
Text
("("),
(String) -> ExtendDoc
Text
(")")).
(doc : ExtendDoc, width? : Int) -> String
render
(),
String
content
="(abc)")
}

打印Json

利用上面定义的函数,我们可以实现一个打印Json的函数。这个函数将递归地处理Json的每个元素,生成相应的布局。

fn 
(x : Json) -> ExtendDoc
pretty
(
Json
x
:
enum Json {
  Null
  True
  False
  Number(Double, repr~ : String?)
  String(String)
  Array(Array[Json])
  Object(Map[String, Json])
}
Json
) ->
enum ExtendDoc {
  Empty
  Line
  Text(String)
  Cat(ExtendDoc, ExtendDoc)
  Nest(Int, ExtendDoc)
  Choice(ExtendDoc, ExtendDoc)
  Group(ExtendDoc)
}
ExtendDoc
{
fn
(Array[ExtendDoc], ExtendDoc, ExtendDoc) -> ExtendDoc
comma_list
(
Array[ExtendDoc]
xs
,
ExtendDoc
l
,
ExtendDoc
r
) {
(
(Int, ExtendDoc) -> ExtendDoc
Nest
(2,
ExtendDoc
softline
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
(xs : Array[ExtendDoc], sep : ExtendDoc) -> ExtendDoc
sepby
(
Array[ExtendDoc]
xs
,
ExtendDoc
comma
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
ExtendDoc
softbreak
))
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
ExtendDoc
softline
)
|>
(m : ExtendDoc, l : ExtendDoc, r : ExtendDoc) -> ExtendDoc
surround
(
ExtendDoc
l
,
ExtendDoc
r
)
|>
(ExtendDoc) -> ExtendDoc
Group
} match
Json
x
{
(Array[Json]) -> Json
Array
(
Array[Json]
elems
) => {
let
Array[ExtendDoc]
elems
=
Array[Json]
elems
.
(self : Array[Json]) -> Iter[Json]

Creates an iterator over the elements of the array.

Parameters:

  • array : The array to create an iterator from.

Returns an iterator that yields each element of the array in order.

Example:

  let arr = [1, 2, 3]
  let mut sum = 0
  arr.iter().each((x) => { sum = sum + x })
  inspect(sum, content="6")
iter
().
(self : Iter[Json], f : (Json) -> ExtendDoc) -> Iter[ExtendDoc]

Transforms the elements of the iterator using a mapping function.

Type Parameters

  • T: The type of the elements in the iterator.
  • R: The type of the transformed elements.

Arguments

  • self - The input iterator.
  • f - The mapping function that transforms each element of the iterator.

Returns

A new iterator that contains the transformed elements.

map
(
(x : Json) -> ExtendDoc
pretty
).
(self : Iter[ExtendDoc]) -> Array[ExtendDoc]

Collects the elements of the iterator into an array.

collect
()
(Array[ExtendDoc], ExtendDoc, ExtendDoc) -> ExtendDoc
comma_list
(
Array[ExtendDoc]
elems
,
(String) -> ExtendDoc
Text
("["),
(String) -> ExtendDoc
Text
("]"))
}
(Map[String, Json]) -> Json
Object
(
Map[String, Json]
pairs
) => {
let
Array[ExtendDoc]
pairs
=
Map[String, Json]
pairs
.
(self : Map[String, Json]) -> Iter[(String, Json)]

Returns the iterator of the hash map, provide elements in the order of insertion.

iter
()
.
(self : Iter[(String, Json)], f : ((String, Json)) -> ExtendDoc) -> Iter[ExtendDoc]

Transforms the elements of the iterator using a mapping function.

Type Parameters

  • T: The type of the elements in the iterator.
  • R: The type of the transformed elements.

Arguments

  • self - The input iterator.
  • f - The mapping function that transforms each element of the iterator.

Returns

A new iterator that contains the transformed elements.

map
(
(String, Json)
p
=>
(ExtendDoc) -> ExtendDoc
Group
(
(String) -> ExtendDoc
Text
(
(String, Json)
p
.
String
0
.
(self : String) -> String

Returns a valid MoonBit string literal representation of a string, add quotes and escape special characters.

Examples

  let str = "Hello \n"
  inspect(str.to_string(), content="Hello \n")
  inspect(str.escape(), content="\"Hello \\n\"")
escape
())
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
(String) -> ExtendDoc
Text
(": ")
(self : ExtendDoc, other : ExtendDoc) -> ExtendDoc
+
(x : Json) -> ExtendDoc
pretty
(
(String, Json)
p
.
Json
1
)))
.
(self : Iter[ExtendDoc]) -> Array[ExtendDoc]

Collects the elements of the iterator into an array.

collect
()
(Array[ExtendDoc], ExtendDoc, ExtendDoc) -> ExtendDoc
comma_list
(
Array[ExtendDoc]
pairs
,
(String) -> ExtendDoc
Text
("{"),
(String) -> ExtendDoc
Text
("}"))
}
(String) -> Json
String
(
String
s
) =>
(String) -> ExtendDoc
Text
(
String
s
.
(self : String) -> String

Returns a valid MoonBit string literal representation of a string, add quotes and escape special characters.

Examples

  let str = "Hello \n"
  inspect(str.to_string(), content="Hello \n")
  inspect(str.escape(), content="\"Hello \\n\"")
escape
())
(Double, repr~ : String?) -> Json
Number
(
Double
i
) =>
(String) -> ExtendDoc
Text
(
Double
i
.
(self : Double) -> String

Converts a double-precision floating-point number to its string representation.

Parameters:

  • self: The double-precision floating-point number to be converted.

Returns a string representation of the double-precision floating-point number.

Example:

  inspect(42.0.to_string(), content="42")
  inspect(3.14159.to_string(), content="3.14159")
  inspect((-0.0).to_string(), content="0")
  inspect(@double.not_a_number.to_string(), content="NaN")
to_string
())
Json
False
=>
(String) -> ExtendDoc
Text
("false")
Json
True
=>
(String) -> ExtendDoc
Text
("true")
Json
Null
=>
(String) -> ExtendDoc
Text
("null")
} }

可以看到在不同的打印宽度下,Json的排版会自动调整。

test {
  let 
Json
json
:
enum Json {
  Null
  True
  False
  Number(Double, repr~ : String?)
  String(String)
  Array(Array[Json])
  Object(Map[String, Json])
}
Json
= {
"key1": "string", "key2": [12345, 67890], "key3": [ { "field1": 1, "field2": 2 }, { "field1": 1, "field2": 2 }, { "field1": [1, 2], "field2": 2 }, ], }
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError

Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of Show implementations and program outputs.

Parameters:

  • object : The object to be inspected. Must implement the Show trait.
  • content : The expected string representation of the object. Defaults to an empty string.
  • location : Source code location information for error reporting. Automatically provided by the compiler.
  • arguments_location : Location information for function arguments in source code. Automatically provided by the compiler.

Throws an InspectError if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.

Example:

  inspect(42, content="42")
  inspect("hello", content="hello")
  inspect([1, 2, 3], content="[1, 2, 3]")
inspect
(
(x : Json) -> ExtendDoc
pretty
(
Json
json
).
(doc : ExtendDoc, width~ : Int) -> String
render
(
Int
width
=80),
String
content
=(
#|{ #| "key1": "string", #| "key2": [12345, 67890], #| "key3": [ #| {"field1": 1, "field2": 2}, #| {"field1": 1, "field2": 2}, #| {"field1": [1, 2], "field2": 2} #| ] #|} ), )
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError

Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of Show implementations and program outputs.

Parameters:

  • object : The object to be inspected. Must implement the Show trait.
  • content : The expected string representation of the object. Defaults to an empty string.
  • location : Source code location information for error reporting. Automatically provided by the compiler.
  • arguments_location : Location information for function arguments in source code. Automatically provided by the compiler.

Throws an InspectError if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.

Example:

  inspect(42, content="42")
  inspect("hello", content="hello")
  inspect([1, 2, 3], content="[1, 2, 3]")
inspect
(
(x : Json) -> ExtendDoc
pretty
(
Json
json
).
(doc : ExtendDoc, width~ : Int) -> String
render
(
Int
width
=30),
String
content
=(
#|{ #| "key1": "string", #| "key2": [12345, 67890], #| "key3": [ #| {"field1": 1, "field2": 2}, #| {"field1": 1, "field2": 2}, #| { #| "field1": [1, 2], #| "field2": 2 #| } #| ] #|} ), )
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError

Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of Show implementations and program outputs.

Parameters:

  • object : The object to be inspected. Must implement the Show trait.
  • content : The expected string representation of the object. Defaults to an empty string.
  • location : Source code location information for error reporting. Automatically provided by the compiler.
  • arguments_location : Location information for function arguments in source code. Automatically provided by the compiler.

Throws an InspectError if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.

Example:

  inspect(42, content="42")
  inspect("hello", content="hello")
  inspect([1, 2, 3], content="[1, 2, 3]")
inspect
(
(x : Json) -> ExtendDoc
pretty
(
Json
json
).
(doc : ExtendDoc, width~ : Int) -> String
render
(
Int
width
=20),
String
content
=(
#|{ #| "key1": "string", #| "key2": [ #| 12345, #| 67890 #| ], #| "key3": [ #| { #| "field1": 1, #| "field2": 2 #| }, #| { #| "field1": 1, #| "field2": 2 #| }, #| { #| "field1": [ #| 1, #| 2 #| ], #| "field2": 2 #| } #| ] #|} ), ) }

总结

本文介绍了如何简单实现一个prettyprinter,使用函数组合的方式来处理结构化数据的打印。通过定义一系列原语和组合函数,我们可以灵活地控制打印格式,并根据屏幕宽度自动调整布局。

当前的实现还可以进一步优化,例如通过记忆化space的计算来提高性能。ExtendDoc::render函数可以增加一个ribbon参数,分别统计当前行的空格和其他文本字数,并且在Group的紧凑模式判断中增加额外的条件,来控制每行的信息密度。另外,还可以增加更多的原语来实现悬挂缩进、最小换行数量等功能。对于更多的设计和实现细节感兴趣的读者,可以参考A prettier printer - Philip Wadler以及Haskell、OCaml等语言的prettyprinter实现。