换行基于三个概念
bh
在盒子b
内触发了一个换行,则新行的缩进只是以下内容的总和:盒子b
的当前缩进 + 盒子b
定义的额外盒子断行缩进 + 断行提示bh
定义的额外提示断行缩进。有四种类型的盒子。(最常用的是“hov”盒子类型,所以第一次阅读时可以跳过其余部分)。
Format.open_hbox
过程获得):在此盒子内,断行提示不会导致换行。Format.open_vbox
过程获得):在此盒子内,每个断行提示都会导致换行。Format.open_hvbox
过程获得):如果可能,整个盒子将写在同一行上;否则,盒子内的每个断行提示都会导致换行。Format.open_box
或Format.open_hovbox
过程获得):在此盒子内,断行提示用于在行上没有更多空间时截断行。有两种“hov”盒子,您可以在下面找到详细信息。在第一次近似中,让我认为这两种“hov”盒子是等价的,并且通过调用Format.open_box
过程获得。让我举个例子。假设我们可以在右边缘(表示没有更多空间)之前写 10 个字符。我们将任何字符表示为-
符号;字符[
和]
表示盒子的打开和关闭,b
代表给漂亮打印引擎的断行提示。
输出“--b--b--”显示如下(b
符号代表下面解释的断行值)
在“h”盒子内
--b--b--
在“v”盒子内
--b
--b
--
在“hv”盒子内
如果有足够的空间在行上打印盒子
--b--b--
但“---b---b---”无法容纳在行上,则写为
---b
---b
---
在“hov”盒子内
如果有足够的空间在行上打印盒子
--b--b--
但如果“---b---b---”无法容纳在行上,则写为
---b---b
---
第一个断行提示不会导致换行,因为行上还有足够的空间。第二个导致换行,因为没有更多空间来打印其后的内容。如果行上剩余的空间更短,则第一个断行提示可能会导致换行,并且“---b---b---”写为
---b
---b
---
断行提示还用于输出空格(如果在遇到断行时行没有被分割,否则换行正确地指示打印项之间的分隔)。您使用print_break sp indent
输出断行提示,并且此 sp 整数用于打印“sp”个空格。因此print_break sp ...
可以认为是:打印sp
个空格或输出换行。
例如,如果在输出“--b--b--”中 b 是break 1 0
,我们将得到
在“h”盒子内
-- -- --
在“v”盒子内
--
--
--
在“hv”盒子内
-- -- --
或者,根据行上剩余的空间
--
--
--
对于“hov”盒子也是如此。
一般来说,使用“format”的打印例程不应直接输出空格:例程应改为使用断行提示。(例如print_space ()
是print_break 1 0
的简写,并输出单个空格或断行。)
用户可以通过两种方式修复换行的缩进
在定义盒子时:当您打开一个盒子时,您可以修复在该盒子内打开的每个换行添加的缩进。
例如:open_hovbox 1
打开一个“hov”盒子,其新行比盒子的初始缩进多 1。对于输出“---[--b--b--b--”,我们将得到
---[--b--b
--b--
使用 open_hovbox 2,我们将得到
---[--b--b
--b--
注意:显示中的 [ 符号在屏幕上不可见,它只是用来具体化漂亮打印盒子的开口。最后一个“屏幕”代表
-----b--b
--b--
在定义导致换行的断行时。如上所述,您使用print_break sp indent
输出断行提示。indent
整数用于修复换行的额外缩进。也就是说,它将添加到发生断行的盒子的默认缩进偏移量中。
例如,如果 [ 代表使用 1 作为额外缩进的“hov”盒子的打开(由open_hovbox 1
获得),并且 b 是print_break 1 2
,则从输出“---[--b--b--b--”,我们将得到
---[-- --
--
--
"hov" 盒子类型细分为两类。
Format.open_hovbox
过程获得):断行提示用于在行上没有更多空间时截断行;如果行上有足够的空间,则不会发生换行。Format.open_box
过程获得):类似于“hov”填充盒子,断行提示用于在行上没有更多空间时截断行;此外,可以显示盒子结构的断行提示会导致换行,即使当前行上有足够的空间。填充和结构“hov”盒子之间的区别由一个在打印结束时关闭盒子和括号的例程显示:对于填充盒子,如果行上有足够的空间,则盒子和括号的关闭不会导致换行,而对于结构盒子,每个断行提示都会导致换行。例如,当打印“[(---[(----[(---b)]b)]b)]”,其中“b”是没有任何额外缩进的断行提示(print_cut ()
)。如果“[”表示“hov”填充盒子的打开(Format.open_hovbox
),“[(---[(----[(---b)]b)]b)]”打印如下
(---
(----
(---)))
如果我们用结构盒子(Format.open_box
)替换填充盒子,则每个在闭合括号之前的断行提示都可以显示盒子结构,如果它导致换行;因此“[(---[(----[(---b)]b)]b)]”打印如下
(---
(----
(---
)
)
)
编写漂亮打印例程时,请遵循以下简单规则
open_*
和Format.close_box
必须像括号一样嵌套)。print_space ()
),除非您明确地不希望在此处断行。例如,假设您想漂亮打印一个 OCaml 定义,更准确地说是一个let rec
ident = expression
值定义。您可能会将前三个空格视为“不可断开的空格”,并将它们直接写入关键字的字符串常量中,并在标识符之前打印"let rec"
,并类似地写入=
以在标识符之后获得一个不可断开的空格;相反,=
之后的空格肯定是一个断行提示,因为在=
之后断行是定义表达式部分的常用(且优雅的)方法。简而言之,通常需要打印不可断开的空格;但是,大多数情况下,空格应被视为断行提示。Format.force_newline
:此过程有效地导致换行,但它也具有不幸的副作用,即部分重新初始化漂亮打印引擎,因此其余打印材料会明显变得混乱。print_newline ()
,这将刷新漂亮打印机表(因此是输出)。(请注意,交互式系统的顶层循环也会在新的输入之前执行此操作。)format 模块提供了一个“a la”printf 的通用打印工具。除了 printf 提供的常用转换工具之外,您还可以直接在格式字符串中编写漂亮打印指示(打开和关闭盒子、指示断行提示等)。
漂亮的打印注释由符号@
引入,直接插入到字符串格式中。几乎任何Format
模块的功能都可以在printf
格式字符串中调用。例如
@[
" 打开一个盒子 (open_box 0)。您可以将类型作为额外参数指定。例如 @[<hov n>
等价于 open_hovbox n
。@]
" 关闭一个盒子 (close_box ()
)。@
" 输出一个可中断空格 (print_space ()
)。@,
" 输出一个断点提示 (print_cut ()
)。@;<n m>
" 发出一个“完整”的断点提示 (print_break n m
)。@.
" 结束漂亮的打印,关闭所有仍然打开的盒子 (print_newline ()
)。例如
printf "@[<1>%s@ =@ %d@ %s@]@." "Prix TTC" 100 "Euros";; Prix TTC = 100 Euros - : unit = ()
让我举一个完整的例子:你能想象到的最短的非平凡例子,也就是lambda演算 :)
因此,问题是漂亮地打印一个具体数据类型的值,该数据类型模拟一个表达式语言,该语言定义函数及其对参数的应用。
首先,我给出lambda项的抽象语法
type lambda = | Lambda of string * lambda | Var of string | Apply of lambda * lambda ;;
我使用format库来打印lambda项
open Format;; let ident = print_string;; let kwd = print_string;; val ident : string -> unit = <fun> val kwd : string -> unit = <fun> let rec print_exp0 = function | Var s -> ident s | lam -> open_hovbox 1; kwd "("; print_lambda lam; kwd ")"; close_box () and print_app = function | e -> open_hovbox 2; print_other_applications e; close_box () and print_other_applications f = match f with | Apply (f, arg) -> print_app f; print_space (); print_exp0 arg | f -> print_exp0 f and print_lambda = function | Lambda (s, lam) -> open_hovbox 1; kwd "\\"; ident s; kwd "."; print_space(); print_lambda lam; close_box() | e -> print_app e;; val print_app : lambda -> unit = <fun> val print_other_applications : lambda -> unit = <fun> val print_lambda : lambda -> unit = <fun>
我们使用fprintf
函数来编写lambda项漂亮打印函数的最通用版本。现在,这些函数获得了一个额外的参数,即一个漂亮的打印格式化程序(ppf参数),打印将在其中发生。这样,打印例程更加通用,因为它们可以打印到程序中定义的任何格式化程序上(打印到文件、stdout
、stderr
,甚至字符串)。此外,漂亮的打印函数现在是组合的,因为它们可以与特殊的%a
转换一起使用,该转换使用用户提供的函数打印fprintf
参数(这些用户提供的函数也以格式化程序作为第一个参数)。
使用fprintf
,lambda项打印例程可以编写如下
open Format;; let ident ppf s = fprintf ppf "%s" s;; let kwd ppf s = fprintf ppf "%s" s;; val ident : Format.formatter -> string -> unit val kwd : Format.formatter -> string -> unit let rec pr_exp0 ppf = function | Var s -> fprintf ppf "%a" ident s | lam -> fprintf ppf "@[<1>(%a)@]" pr_lambda lam and pr_app ppf = function | e -> fprintf ppf "@[<2>%a@]" pr_other_applications e and pr_other_applications ppf f = match f with | Apply (f, arg) -> fprintf ppf "%a@ %a" pr_app f pr_exp0 arg | f -> pr_exp0 ppf f and pr_lambda ppf = function | Lambda (s, lam) -> fprintf ppf "@[<1>%a%a%a@ %a@]" kwd "\\" ident s kwd "." pr_lambda lam | e -> pr_app ppf e ;; val pr_app : Format.formatter -> lambda -> unit val pr_other_applications : Format.formatter -> lambda -> unit val pr_lambda : Format.formatter -> lambda -> unit
给定这些通用的打印例程,打印到stdout
或stderr
的过程仅仅是部分应用的问题
let print_lambda = pr_lambda std_formatter;; let eprint_lambda = pr_lambda err_formatter;; val print_lambda : lambda -> unit val eprint_lambda : lambda -> unit