prettyprinter:使用函数组合解决结构化数据打印问题
结构化数据的打印是编程中常见的问题,尤其是在调试和日志记录时。如何展示复杂的数据结构,并能够根据屏幕宽度调整排版?例如,对于一个数组字面量 [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
#|)
),
)
}
我们先通过组合Empty
和Line
的方式定义了一个在紧凑 模式下不换行的 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)
autoline
和 autobreak
实现一种类似于文字编辑器的排版:尽可能多地将内容放进一行内,溢出则换行。
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
xArray[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实现。