模块 Format

module Format: sig .. end

美观打印。

如果您不熟悉此模块,请查看下面的 示例

此模块实现了一个美观打印功能,用于在 '美观打印框''语义标签' 中格式化值,并结合一套 类似 printf 的函数。美观打印器会在指定的 断点提示 处换行,并根据框结构缩进行。类似地,语义标签 可用于将文本呈现与其内容分离。

此美观打印功能是在抽象 格式化器 之上实现的,这些格式化器提供基本的输出函数。一些格式化器是预定义的,最著名的是

Format 模块中的大多数函数都具有两种变体:一种是短版本,它操作使用 Format.get_std_formatter 获取的当前域的标准格式化器;另一种是通用版本,以 pp_ 为前缀,并以格式化器作为其第一个参数。对于操作当前域的标准格式化器的版本,对 Format.get_std_formatter 的调用将延迟到收到最后一个参数为止。

可以使用 Format.formatter_of_out_channelFormat.formatter_of_bufferFormat.formatter_of_symbolic_output_buffer 或使用 自定义格式化器 创建更多格式化器。

警告:由于 格式化器 包含可变状态,因此在没有同步的情况下,在多个域上并行使用同一个格式化器是不安全的。

如果多个域使用预定义的格式化器(如通过 Format.get_std_formatterFormat.get_err_formatter 获取的格式化器)写入同一个输出通道,则这些域的输出将在格式化器刷新时(例如使用 Format.print_flush)相互交织。从 Format.formatter_of_out_channel(在标准输出通道或其他通道上)获取的格式化器不会执行此同步。


简介

您可以将此模块视为对 printf 功能的扩展,以提供自动换行功能。在您的常规 printf 格式字符串中添加美观打印注释会为您提供漂亮的缩进和换行。美观打印注释在函数 Format.fprintf 的文档中进行了描述。

您还可以使用此模块提供的显式美观打印框管理和打印函数。这种样式比简洁的 fprintf 格式字符串更基本但更冗长。

例如,序列 open_box 0; print_string "x ="; print_space ();
    print_int 1; close_box (); print_newline ()
在美观打印框中打印 x = 1,可以简写为 printf "@[%s@ %i@]@." "x =" 1,甚至可以更短地写为 printf "@[x =@ %i@]@." 1

此库的休闲用户经验法则

如果没有任何打开的美观打印框,则美观打印命令的行为未定义。通过以下 open_ 函数之一打开的每个框都必须使用 close_box 关闭,以确保格式正确。否则,一些在框中打印的内容可能不会输出,或者可能格式不正确。

在交互式使用的情况下,每个短语都在标准美观打印器的初始状态下执行:在每个短语执行后,交互式系统会关闭所有打开的美观打印框,刷新所有待处理的文本,并重置标准美观打印器。

警告:将此模块的美观打印函数调用与 Stdlib 低级输出函数调用混合在一起可能会导致错误。

美观打印函数输出的内容会延迟到美观打印器队列中,并按顺序堆叠,以便计算正确的换行。相反,基本 I/O 输出函数会直接写入其输出设备。因此,基本 I/O 函数的输出可能会出现在之前调用的美观打印函数的输出之前。例如,
    Stdlib.print_string "<";
    Format.print_string "PRETTY";
    Stdlib.print_string ">";
    Format.print_string "TEXT";
   
导致输出 <>PRETTYTEXT

格式化器

type formatter 

与美观打印器(也称为格式化器)及其所有机制相对应抽象数据。另请参阅 定义格式化器

美观打印框

美观打印引擎使用美观打印框和断点提示的概念来驱动美观打印器的缩进和换行行为。

每个不同的美观打印框类型都会引入一个特定的换行策略

请注意,换行策略是特定于框的:框的策略不决定内部框的策略。例如,如果一个垂直框嵌套在一个水平框中,则垂直框内的所有断点提示都会换行。

此外,在 最大缩进限制 后打开一个框会换行,无论该框最终是否适合该行。

val pp_open_box : formatter -> int -> unit
val open_box : int -> unit

pp_open_box ppf d 在格式化器 ppf 中打开一个新的压缩美观打印框,偏移量为 d

在这个框内,美观打印器会在每行上打印尽可能多的内容。

如果当前行没有更多空间来打印框的剩余部分,则断点提示会换行。

在这个框内,美观打印器会强调框结构:如果结构框不能完全适合一条简单的行,则如果拆分“向左移动”(即新行的缩进小于当前行的缩进),则断点提示也会换行。

此框是通用的美观打印框。

如果美观打印器在该框内换行,则会将偏移量 d 添加到当前缩进。

val pp_close_box : formatter -> unit -> unit
val close_box : unit -> unit

关闭最近打开的美观打印框。

val pp_open_hbox : formatter -> unit -> unit
val open_hbox : unit -> unit

pp_open_hbox ppf () 打开一个新的“水平”美观打印框。

此框将内容打印在一行上。

水平框内的断点提示永远不会换行。(行可能会在更深层的框内换行)。

val pp_open_vbox : formatter -> int -> unit
val open_vbox : int -> unit

pp_open_vbox ppf d 打开一个新的“垂直”美观打印框,偏移量为 d

此框将内容打印在框中断点提示的数量行上。

垂直框内的每个断点提示都会换行。

如果美观打印器在该框内换行,则会将 d 添加到当前缩进。

val pp_open_hvbox : formatter -> int -> unit
val open_hvbox : int -> unit

pp_open_hvbox ppf d 打开一个新的“水平/垂直”美观打印框,偏移量为 d

如果此框适合一行,则其行为类似于水平框,否则其行为类似于垂直框。

如果美观打印器在该框内换行,则会将 d 添加到当前缩进。

val pp_open_hovbox : formatter -> int -> unit
val open_hovbox : int -> unit

pp_open_hovbox ppf d 打开一个新的“水平或垂直”美观打印框,偏移量为 d

此框会尽可能地在每行上打印内容。

如果当前行没有更多空间来打印框的剩余部分,则断点提示会换行。

如果美观打印器在该框内换行,则会将 d 添加到当前缩进。

格式化函数

val pp_print_string : formatter -> string -> unit
val print_string : string -> unit

pp_print_string ppf s 在当前美观打印框中打印 s

val pp_print_bytes : formatter -> bytes -> unit
val print_bytes : bytes -> unit

pp_print_bytes ppf b 在当前美观打印框中打印 b

val pp_print_as : formatter -> int -> string -> unit
val print_as : int -> string -> unit

pp_print_as ppf len s 在当前美观打印框中打印 s。美观打印器会将 s 格式化为长度为 len 的字符串。

val pp_print_int : formatter -> int -> unit
val print_int : int -> unit

在当前美观打印框中打印一个整数。

val pp_print_float : formatter -> float -> unit
val print_float : float -> unit

在当前美观打印框中打印一个浮点数。

val pp_print_char : formatter -> char -> unit
val print_char : char -> unit

在当前美观打印框中打印一个字符。

val pp_print_bool : formatter -> bool -> unit
val print_bool : bool -> unit

在当前美观打印框中打印一个布尔值。

val pp_print_nothing : formatter -> unit -> unit

不打印任何内容。

断点提示

“断点提示”告诉美观打印器输出一些空格或换行,具体取决于当前美观打印框的拆分规则更适合哪种方式。

断点提示用于分隔打印项,并且是让美观打印器正确换行和缩进项所必需的。

简单的断点提示是

注意:“空格”和“换行”的概念对于美观打印引擎来说是抽象的,因为这些概念可以由程序员完全重新定义。但是,在美观打印器的默认设置中,“输出空格”简单地意味着打印一个空格字符(ASCII 码 32),而“换行”则意味着打印一个换行符(ASCII 码 10)。

val pp_print_space : formatter -> unit -> unit
val print_space : unit -> unit

pp_print_space ppf () 发出一个“空格”断点提示:美观打印器可能在此时换行,否则它会打印一个空格。

pp_print_space ppf () 等同于 pp_print_break ppf 1 0

val pp_print_cut : formatter -> unit -> unit
val print_cut : unit -> unit

pp_print_cut ppf () 会发出一个“cut”断点提示:美化打印程序可能会在该点拆分行,否则它不会打印任何内容。

pp_print_cut ppf () 等同于 pp_print_break ppf 0 0

val pp_print_break : formatter -> int -> int -> unit
val print_break : int -> int -> unit

pp_print_break ppf nspaces offset 会发出一个“full”断点提示:美化打印程序可能会在该点拆分行,否则它会打印 nspaces 个空格。

如果美化打印程序拆分了行,offset 将添加到当前缩进中。

val pp_print_custom_break : formatter ->
fits:string * int * string -> breaks:string * int * string -> unit

pp_print_custom_break ppf ~fits:(s1, n, s2) ~breaks:(s3, m, s4) 会发出一个自定义断点提示:美化打印程序可能会在该点拆分行。

如果它没有拆分行,则会发出 s1,然后是 n 个空格,然后是 s2

如果它拆分了行,则会发出 s3 字符串,然后是一个缩进(根据框规则),然后是 m 个空格的偏移量,然后是 s4 字符串。

虽然 nmformatter_out_functions.out_indent 处理,但字符串将由 formatter_out_functions.out_string 处理。这允许一个自定义格式化程序以不同的方式处理缩进,例如,输出 <br/> 标签或 &nbsp; 实体。

自定义断点在您想要更改断开或未断开情况下打印的可见(非空格)字符时非常有用。例如,在打印列表  [a; b; c]  时,您可能希望在它垂直打印时添加一个尾随分号

[
  a;
  b;
  c;
]
   

您可以按照以下步骤执行此操作

printf "@[<v 0>[@;<0 2>@[<v 0>a;@,b;@,c@]%t]@]@\n"
  (pp_print_custom_break ~fits:("", 0, "") ~breaks:(";", 0, ""))
   
val pp_force_newline : formatter -> unit -> unit
val force_newline : unit -> unit

在当前美化打印框中强制换行。

美化打印程序必须在此处拆分行,

这不是美化打印的正常方式,因为强制行拆分可能会干扰当前行计数器和框大小计算。在封闭的垂直框中使用断点提示是一个更好的选择。

val pp_print_if_newline : formatter -> unit -> unit
val print_if_newline : unit -> unit

如果前一行刚刚被拆分,则执行下一个格式化命令。否则,忽略下一个格式化命令。

美观打印终止

val pp_print_flush : formatter -> unit -> unit
val print_flush : unit -> unit

美化打印结束:将美化打印程序重置为初始状态。

所有打开的美化打印框都已关闭,所有待处理的文本都已打印。此外,美化打印程序的低级输出设备被刷新,以确保所有待处理的文本都真正显示。

注意:在美化打印例程的正常过程中,永远不要使用 print_flush,因为美化打印程序使用复杂的缓冲机制来正确缩进输出;随机刷新这些缓冲区会与美化打印程序策略冲突,并导致糟糕的渲染效果。

只有在显示所有待处理材料是强制性的情况下(例如,在交互式使用时,您希望用户阅读某些文本)并且重置美化打印程序状态不会干扰进一步的美化打印时,才考虑使用 print_flush

警告:如果美化打印程序的输出设备是输出通道,则对 print_flush 的重复调用意味着对 flush 的重复调用,以刷新输出通道;这些显式的刷新调用可能会破坏输出通道的缓冲策略,并可能极大地影响效率。

val pp_print_newline : formatter -> unit -> unit
val print_newline : unit -> unit

美化打印结束:将美化打印程序重置为初始状态。

所有打开的美化打印框都已关闭,所有待处理的文本都已打印。

等同于 Format.print_flush,在设备被刷新之前立即在美化打印程序的低级输出设备上发出换行符。有关 Format.print_flush 的相应注意事项,请参阅。

注意:这不是输出换行的正常方式;首选方法是在垂直美化打印框中使用断点提示。

边距

val pp_infinity : int

pp_infinity 是边距的最大大小。其确切值取决于实现,但保证大于 109

val pp_set_margin : formatter -> int -> unit
val set_margin : int -> unit

pp_set_margin ppf d 将右边距设置为 d(以字符为单位):美化打印程序会根据给定的断点提示拆分超过右边距的行。将边距设置为 d 意味着格式化引擎的目标是每行最多打印 d-1 个字符。如果 d 小于 2,则不会发生任何情况。如果 d >= Format.pp_infinity,则右边距设置为 Format.pp_infinity - 1。如果 d 小于当前最大缩进限制,则最大缩进限制会降低,同时尝试保持最小比率 max_indent/margin>=50%,如果可能,则保留当前差值 margin - max_indent

另请参阅 Format.pp_set_geometry.

val pp_get_margin : formatter -> unit -> int
val get_margin : unit -> int

返回右边距的位置。

最大缩进限制

val pp_set_max_indent : formatter -> int -> unit
val set_max_indent : int -> unit

pp_set_max_indent ppf d 将行的最大缩进限制设置为 d(以字符为单位):一旦达到此限制,新的美化打印框将被拒绝到左侧,除非封闭框完全适合当前行。例如,

 set_margin 10; set_max_indent 5; printf "@[123456@[7@]89A@]@." 

产生

    123456
    789A
  

因为嵌套框 "@[7@]" 在最大缩进限制(7>5)之后打开,并且其父框不适合当前行。通过缩短父框的长度以使其适合一行

 printf "@[123456@[7@]89@]@." 

或在最大缩进限制之前打开一个中间框,该框适合当前行

 printf "@[123@[456@[7@]89@]A@]@." 

可以避免内部框向左拒绝,并分别打印 "123456789""123456789A" 。还要注意,垂直框永远不适合一行,而水平框始终完全适合当前行。打开框可能会拆分一行,而内容可能已适合。如果这种行为有问题,可以通过将最大缩进限制设置为 margin - 1 来缩短。请注意,将最大缩进限制设置为 margin 是无效的。

如果 d 小于 2,则不会发生任何情况。

如果 d 大于当前边距,则会忽略它,并保留当前最大缩进限制。

另请参阅 Format.pp_set_geometry.

val pp_get_max_indent : formatter -> unit -> int
val get_max_indent : unit -> int

返回最大缩进限制(以字符为单位)。

几何

几何函数可用于同时操作耦合变量,即边距和最大缩进限制。

type geometry = {
   max_indent : int;
   margin : int;
}
val check_geometry : geometry -> bool

检查格式化程序几何形状是否有效:1 < max_indent < margin < Format.pp_infinity

val pp_set_geometry : formatter -> max_indent:int -> margin:int -> unit
val set_geometry : max_indent:int -> margin:int -> unit
val pp_safe_set_geometry : formatter -> max_indent:int -> margin:int -> unit
val safe_set_geometry : max_indent:int -> margin:int -> unit

pp_set_geometry ppf ~max_indent ~margin 同时设置 ppf 的边距和最大缩进限制。

1 < max_indent < margin < Format.pp_infinity 时,pp_set_geometry ppf ~max_indent ~margin 等同于 pp_set_margin ppf margin; pp_set_max_indent ppf max_indent;并且避免了细微错误的 pp_set_max_indent ppf max_indent; pp_set_margin ppf margin

在此域之外,pp_set_geometry 会引发无效参数异常,而 pp_safe_set_geometry 不会执行任何操作。

val pp_update_geometry : formatter -> (geometry -> geometry) -> unit

pp_update_geometry ppf (fun geo -> { geo with ... }) 允许您以一种对 geometry 记录扩展为新字段稳健的方式更新格式化程序的几何形状。

如果返回的几何形状不满足 Format.check_geometry,则引发无效参数异常。

val update_geometry : (geometry -> geometry) -> unit
val pp_get_geometry : formatter -> unit -> geometry
val get_geometry : unit -> geometry

返回格式化程序的当前几何形状

最大格式化深度

最大格式化深度是同时打开的美化打印框的最大数量。

嵌套更深的框内的材料将以省略号的形式打印(更确切地说,是以 Format.get_ellipsis_text () 返回的文本形式打印)。

val pp_set_max_boxes : formatter -> int -> unit
val set_max_boxes : int -> unit

pp_set_max_boxes ppf max 设置同时打开的美化打印框的最大数量。

嵌套更深的框内的材料将以省略号的形式打印(更确切地说,是以 Format.get_ellipsis_text () 返回的文本形式打印)。

如果 max 小于 2,则不会发生任何情况。

val pp_get_max_boxes : formatter -> unit -> int
val get_max_boxes : unit -> int

返回在出现省略号之前允许的美化打印框的最大数量。

val pp_over_max_boxes : formatter -> unit -> bool
val over_max_boxes : unit -> bool

测试是否已打开允许的美化打印框的最大数量。

制表符框

制表符框将材料打印在分成固定长度单元格的行上。制表符框提供了一种简单的方式来显示左对齐文本的垂直列。

此框具有命令 set_tab 来定义单元格边界,以及命令 print_tab 来在单元格之间移动,并在行上没有更多单元格可打印时拆分行。

注意:制表符框内的打印是面向行的,因此在制表符框内任意拆分行会导致糟糕的渲染效果。但是,控制使用制表符框允许在模块 Format 中简单地打印列。

val pp_open_tbox : formatter -> unit -> unit
val open_tbox : unit -> unit

open_tbox () 打开一个新的制表符框。

此框打印分成固定宽度单元格的行。

在制表符框内,特殊的制表符标记定义了行上的兴趣点(例如,用于分隔单元格边界)。函数 Format.set_tab 在插入点设置一个制表符标记。

制表符框具有特定的制表符断点,用于移动到下一个制表符标记或拆分行。函数 Format.print_tbreak 打印一个制表符断点。

val pp_close_tbox : formatter -> unit -> unit
val close_tbox : unit -> unit

关闭最近打开的制表符框。

val pp_set_tab : formatter -> unit -> unit
val set_tab : unit -> unit

在当前插入点设置一个制表符标记。

val pp_print_tab : formatter -> unit -> unit
val print_tab : unit -> unit

print_tab () 发出一个“next”制表符断点提示:如果尚未设置在制表符标记上,则插入点将移动到右侧的第一个制表符标记,或者美化打印程序将拆分行,插入点将移动到最左侧的制表符标记。

它等同于 print_tbreak 0 0

val pp_print_tbreak : formatter -> int -> int -> unit
val print_tbreak : int -> int -> unit

print_tbreak nspaces offset 会发出一个“full”制表符断点提示。

如果尚未设置在制表符标记上,则插入点将移动到右侧的第一个制表符标记,美化打印程序将打印 nspaces 个空格。

如果右侧没有下一个制表符标记,美化打印程序将在此处拆分行,然后插入点将移动到框的最左侧的制表符标记。

如果美化打印程序拆分了行,offset 将添加到当前缩进中。

省略号

val pp_set_ellipsis_text : formatter -> string -> unit
val set_ellipsis_text : string -> unit

当打开过多美化打印框时,设置省略号的文本(默认情况下为单个点,.)。

val pp_get_ellipsis_text : formatter -> unit -> string
val get_ellipsis_text : unit -> string

返回省略号的文本。

语义标签

type stag = ..

语义标签(或简称为标签)是用户定义的注释,用于将用户特定的操作与打印的实体相关联。

语义标签的常见用途是文本装饰,以获得特定字体或文本大小的渲染以用于显示设备,或标记实体的界限(例如 HTML 或 TeX 元素或终端转义序列)。语义标签的更复杂用法可以处理美化打印机行为的动态修改,以便正确打印某些特定标签内的材料。例如,我们可以定义一个 RGB 标签,如下所示

type stag += RGB of {r:int;g:int;b:int}

为了正确地界定打印的实体,必须在实体之前打开语义标签,并在实体之后关闭语义标签。语义标签必须像使用 Format.pp_open_stagFormat.pp_close_stag 的括号一样,正确地嵌套。

标签特定的操作在每次打开或关闭标签时都会发生。在每次发生时,将执行两种操作:标签标记标签打印

  • 标签标记操作是更简单的标签特定操作:它只是将标签特定的字符串写入格式化程序的输出设备。标签标记不会干扰行拆分计算。
  • 标签打印操作是更复杂的标签特定操作:它可以将任意材料打印到格式化程序。标签打印与当前的美化打印机操作紧密相关。

粗略地说,标签标记通常用于在渲染设备中获得更好的文本渲染,而标签打印允许微调打印例程,以便根据语义标签以不同的方式打印相同的实体(即打印附加材料,甚至省略输出的一部分)。

更准确地说:当打开或关闭语义标签时,会执行连续的“标签打印”和“标签标记”操作

  • 标签打印语义标签意味着使用标签名称作为参数调用格式化程序特定的函数 print_open_stag(或 print_close_stag):该标签打印函数然后可以将任何常规材料打印到格式化程序(以便该材料像往常一样排队在格式化程序队列中,以便进一步进行行拆分计算)。
  • 标签标记语义标签意味着使用标签名称作为参数调用格式化程序特定的函数 mark_open_stag(或 mark_close_stag):该标签标记函数然后可以返回“标签打开标记”(或“标签关闭标记”),以便直接输出到格式化程序的输出设备中。

语义标签标记字符串直接写入格式化程序的输出设备,不被视为驱动行拆分的打印材料的一部分(换句话说,与标签标记对应的字符串的长度在行拆分中被视为零)。

因此,语义标签处理在某种程度上对美化打印是透明的,不会干扰正常的缩进。因此,单个美化打印例程可以根据标签的处理方式输出简单的“逐字”材料或更丰富的装饰输出。默认情况下,标签处于非活动状态,因此输出不包含标签信息。一旦 set_tags 设置为 true,美化打印机引擎就会遵守标签并相应地装饰输出。

默认标签标记函数的行为类似于 HTML:字符串标签 括在“<”和“>”中,而其他标签则被忽略;因此,标签字符串 "t" 的打开标记为 "<t>",关闭标记为 "</t>"

默认标签打印函数什么也不做。

标签标记和标签打印函数是用户可定义的,可以通过调用 Format.set_formatter_stag_functions 来设置。

语义标签操作可以通过 Format.set_tags 打开或关闭。标签标记操作可以通过 Format.set_mark_tags 打开或关闭。标签打印操作可以通过 Format.set_print_tags 打开或关闭。

type tag = string 
type stag += 
| String_tag of tag (*

String_tag s 是一个字符串标签 s。字符串标签可以通过显式使用构造函数 String_tag 或使用专用格式语法 "@{<s> ... @}" 插入。

  • 由于 4.08
*)
val pp_open_stag : formatter -> stag -> unit
val open_stag : stag -> unit

pp_open_stag ppf t 打开名为 t 的语义标签。

格式化程序的 print_open_stag 标签打印函数使用 t 作为参数调用;然后,通过 mark_open_stag t 给出的 t 的打开标签标记将写入格式化程序的输出设备中。

val pp_close_stag : formatter -> unit -> unit
val close_stag : unit -> unit

pp_close_stag ppf () 关闭最近打开的语义标签 t

通过 mark_close_stag t 给出的关闭标签标记将写入格式化程序的输出设备中;然后,格式化程序的 print_close_stag 标签打印函数使用 t 作为参数调用。

val pp_set_tags : formatter -> bool -> unit
val set_tags : bool -> unit

pp_set_tags ppf b 打开或关闭语义标签的处理(默认情况下关闭)。

val pp_set_print_tags : formatter -> bool -> unit
val set_print_tags : bool -> unit

pp_set_print_tags ppf b 打开或关闭标签打印操作。

val pp_set_mark_tags : formatter -> bool -> unit
val set_mark_tags : bool -> unit

pp_set_mark_tags ppf b 打开或关闭标签标记操作。

val pp_get_print_tags : formatter -> unit -> bool
val get_print_tags : unit -> bool

返回标签打印操作的当前状态。

val pp_get_mark_tags : formatter -> unit -> bool
val get_mark_tags : unit -> bool

返回标签标记操作的当前状态。

val pp_set_formatter_out_channel : formatter -> out_channel -> unit

重定向标准格式化器输出

val set_formatter_out_channel : out_channel -> unit

将标准美化打印机输出重定向到给定的通道。(标准格式化程序的所有输出函数都设置为默认输出函数,这些函数打印到给定的通道。)

set_formatter_out_channel 等效于 Format.pp_set_formatter_out_channel std_formatter

val pp_set_formatter_output_functions : formatter -> (string -> int -> int -> unit) -> (unit -> unit) -> unit
val set_formatter_output_functions : (string -> int -> int -> unit) -> (unit -> unit) -> unit

pp_set_formatter_output_functions ppf out flush 将标准美化打印机输出函数重定向到函数 outflush

函数 out 执行所有美化打印机的字符串输出。它使用字符串 s、起始位置 p 和字符数 n 调用;它应该输出 s 的字符 pp + n - 1

每当美化打印机刷新时(通过转换 %! 或美化打印指示 @?@.,或使用低级函数 print_flushprint_newline),就会调用函数 flush

val pp_get_formatter_output_functions : formatter -> unit -> (string -> int -> int -> unit) * (unit -> unit)
val get_formatter_output_functions : unit -> (string -> int -> int -> unit) * (unit -> unit)

返回标准美化打印机的当前输出函数。

重新定义格式化器输出

模块 Format 足够灵活,可以让你完全重新定义美化打印输出的含义:你可以提供自己的函数来定义如何处理缩进、行拆分,甚至打印所有必须打印的字符!

重新定义输出函数

type formatter_out_functions = {
   out_string : string -> int -> int -> unit;
   out_flush : unit -> unit;
   out_newline : unit -> unit;
   out_spaces : int -> unit;
   out_indent : int -> unit; (*
  • 由于 4.06
*)
}

特定于格式化程序的输出函数集

  • 函数 out_string 执行所有美化打印机的字符串输出。它使用字符串 s、起始位置 p 和字符数 n 调用;它应该输出 s 的字符 pp + n - 1
  • 函数 out_flush 刷新美化打印机的输出设备。
  • out_newline 用于在美化打印机拆分行时打开新行。
  • 函数 out_spaces 在换行提示导致空格而不是行拆分时输出空格。它使用要输出的空格数调用。
  • 函数 out_indent 在美化打印机拆分行时执行新行缩进。它使用新行的缩进值调用。

默认情况下

  • 字段 out_stringout_flush 是特定于输出设备的;(例如,output_stringflush 用于 out_channel 设备,或 Buffer.add_substringignore 用于 Buffer.t 输出设备),
  • 字段 out_newline 等效于 out_string "\n" 0 1
  • 字段 out_spacesout_indent 等效于 out_string (String.make n ' ') 0 n
val pp_set_formatter_out_functions : formatter -> formatter_out_functions -> unit
val set_formatter_out_functions : formatter_out_functions -> unit

pp_set_formatter_out_functions ppf out_funsppf 的所有美化打印机输出函数设置为参数 out_funs 中的函数,

这样,你就可以改变缩进的含义(它可以是除了打印空格字符以外的其他东西)和打开新行的含义(它可以与应用程序当前需要的任何其他操作相连)。

函数 out_spacesout_newline 的合理默认值分别为 out_funs.out_string (String.make n ' ') 0 nout_funs.out_string "\n" 0 1

val pp_get_formatter_out_functions : formatter -> unit -> formatter_out_functions
val get_formatter_out_functions : unit -> formatter_out_functions

返回美化打印机的当前输出函数,包括行拆分和缩进函数。用于记录当前设置并在之后恢复它。

重新定义语义标签操作

type formatter_stag_functions = {
   mark_open_stag : stag -> string;
   mark_close_stag : stag -> string;
   print_open_stag : stag -> unit;
   print_close_stag : stag -> unit;
}

特定于格式化程序的语义标签处理函数:mark 版本是“标签标记”函数,将字符串标记与标签相关联,以便美化打印机引擎将这些标记作为 0 长度标记写入格式化程序的输出设备中。 print 版本是“标签打印”函数,可以在关闭或打开标签时执行常规打印。

val pp_set_formatter_stag_functions : formatter -> formatter_stag_functions -> unit
val set_formatter_stag_functions : formatter_stag_functions -> unit

pp_set_formatter_stag_functions ppf tag_funs 更改打开和关闭语义标签操作的含义,以便在 ppf 上打印时使用 tag_funs 中的函数。

当使用名称 t 打开语义标签时,字符串 t 将传递给打开的标签标记函数(记录 tag_funsmark_open_stag 字段),该函数必须返回该名称的打开标签标记。当下一个调用 close_stag () 发生时,语义标签名称 t 将被发送回关闭的标签标记函数(记录 tag_funsmark_close_stag 字段),该函数必须返回该名称的关闭标签标记。

记录的 print_ 字段包含在标签打开和标签关闭时调用的标签打印函数,用于在漂亮打印队列中输出常规材料。

val pp_get_formatter_stag_functions : formatter -> unit -> formatter_stag_functions
val get_formatter_stag_functions : unit -> formatter_stag_functions

返回标准漂亮打印器的当前语义标签操作函数。

定义格式化器

定义新的格式化程序允许在多个输出设备上并行输出不相关的材料。格式化程序的所有参数都是特定于格式化程序的:右边界、最大缩进限制、同时打开的最大漂亮打印框数、省略号等,都是特定于每个格式化程序的,可以独立地进行固定。

例如,给定一个 Buffer.t 缓冲区 bFormat.formatter_of_buffer b 返回一个使用缓冲区 b 作为其输出设备的新格式化程序。类似地,给定一个 out_channel 输出通道 ocFormat.formatter_of_out_channel oc 返回一个使用通道 oc 作为其输出设备的新格式化程序。

或者,给定 out_funs,一个完整的格式化程序输出函数集,那么 Format.formatter_of_out_functions out_funs 计算一个使用这些函数进行输出的新格式化程序。

val formatter_of_out_channel : out_channel -> formatter

formatter_of_out_channel oc 返回一个写入相应输出通道 oc 的新格式化程序。

val synchronized_formatter_of_out_channel : out_channel -> formatter Domain.DLS.key

synchronized_formatter_of_out_channel oc 返回域本地状态的键,该状态保存用于写入相应输出通道 oc 的域本地格式化程序。

当格式化程序与多个域一起使用时,来自域的输出将在格式化程序被刷新的位置彼此交错,例如使用 Format.print_flush

val std_formatter : formatter

初始域的标准格式化程序,用于写入标准输出。

它被定义为 Format.formatter_of_out_channel stdout

val get_std_formatter : unit -> formatter

get_std_formatter () 返回当前域的标准格式化程序,用于写入标准输出。

val err_formatter : formatter

初始域的格式化程序,用于写入标准错误。

它被定义为 Format.formatter_of_out_channel stderr

val get_err_formatter : unit -> formatter

get_err_formatter () 返回当前域的格式化程序,用于写入标准错误。

val formatter_of_buffer : Buffer.t -> formatter

formatter_of_buffer b 返回一个写入缓冲区 b 的新格式化程序。在漂亮打印结束时,必须使用 Format.pp_print_flushFormat.pp_print_newline 刷新格式化程序,以将所有待处理的材料打印到缓冲区中。

val stdbuf : Buffer.t

初始域的字符串缓冲区,其中 str_formatter 写入。

val get_stdbuf : unit -> Buffer.t

get_stdbuf () 返回当前域的字符串缓冲区,其中当前域的字符串格式化程序写入。

val str_formatter : formatter

初始域的格式化程序,用于输出到 Format.stdbuf 字符串缓冲区。

str_formatter 被定义为 Format.formatter_of_buffer Format.stdbuf

val get_str_formatter : unit -> formatter

当前域的格式化程序,用于输出到当前域的字符串缓冲区。

val flush_str_formatter : unit -> string

返回使用当前域的 str_formatter 打印的材料,刷新格式化程序并重置相应的缓冲区。

val make_formatter : (string -> int -> int -> unit) -> (unit -> unit) -> formatter

make_formatter out flush 返回一个使用函数 out 输出并使用函数 flush 刷新新的格式化程序。

例如,

    make_formatter
      (Stdlib.output_substring oc)
      (fun () -> Stdlib.flush oc)
  

返回一个到 out_channel oc 的格式化程序。

val make_synchronized_formatter : (string -> int -> int -> unit) ->
(unit -> unit) -> formatter Domain.DLS.key

make_synchronized_formatter out flush 返回域本地状态的键,该状态保存域本地格式化程序,该格式化程序使用函数 out 输出并使用函数 flush 刷新。

当格式化程序与多个域一起使用时,来自域的输出将在格式化程序被刷新的位置彼此交错,例如使用 Format.print_flush

val formatter_of_out_functions : formatter_out_functions -> formatter

formatter_of_out_functions out_funs 返回一个使用输出函数集 out_funs 写入的新格式化程序。

有关参数 out_funs 的含义,请参阅类型 Format.formatter_out_functions 的定义。

符号美观打印

符号漂亮打印是使用符号格式化程序进行的漂亮打印,即输出符号漂亮打印项目的格式化程序。

使用符号格式化程序时,将执行所有常规的漂亮打印活动,但输出材料是符号化的,并存储在输出项目缓冲区中。在漂亮打印结束时,刷新输出缓冲区允许在执行低级输出操作之前对符号输出进行后处理。

在实践中,首先使用以下代码定义一个符号输出缓冲区 b

像往常一样使用符号格式化程序 ppf,并在漂亮打印结束时通过使用以下代码刷新符号输出缓冲区 sob 来检索符号项目:

type symbolic_output_item = 
| Output_flush (*

符号刷新命令

*)
| Output_newline (*

符号换行命令

*)
| Output_string of string (*

Output_string s: 字符串 s 的符号输出

*)
| Output_spaces of int (*

Output_spaces n: 输出 n 个空格的符号命令

*)
| Output_indent of int (*

Output_indent i: 大小为 i 的符号缩进

*)

符号漂亮打印程序生成的项目

type symbolic_output_buffer 

符号漂亮打印程序的输出缓冲区。

val make_symbolic_output_buffer : unit -> symbolic_output_buffer

make_symbolic_output_buffer () 返回一个用于符号输出的新缓冲区。

val clear_symbolic_output_buffer : symbolic_output_buffer -> unit

clear_symbolic_output_buffer sob 重置缓冲区 sob

val get_symbolic_output_buffer : symbolic_output_buffer -> symbolic_output_item list

get_symbolic_output_buffer sob 返回缓冲区 sob 的内容。

val flush_symbolic_output_buffer : symbolic_output_buffer -> symbolic_output_item list

flush_symbolic_output_buffer sob 返回缓冲区 sob 的内容并重置缓冲区 sobflush_symbolic_output_buffer sob 等效于 let items = get_symbolic_output_buffer sob in
   clear_symbolic_output_buffer sob; items

val add_symbolic_output_item : symbolic_output_buffer -> symbolic_output_item -> unit

add_symbolic_output_item sob itm 将项目 itm 添加到缓冲区 sob

val formatter_of_symbolic_output_buffer : symbolic_output_buffer -> formatter

formatter_of_symbolic_output_buffer sob 返回一个输出到 symbolic_output_buffer sob 的符号格式化程序。

便捷格式化函数。

val pp_print_iter : ?pp_sep:(formatter -> unit -> unit) ->
(('a -> unit) -> 'b -> unit) ->
(formatter -> 'a -> unit) -> formatter -> 'b -> unit

pp_print_iter ~pp_sep iter pp_v ppf v 使用 pp_vppf 上格式化 iter 对集合 v 中值的迭代。迭代之间用 pp_sep 分隔(默认为 Format.pp_print_cut)。

val pp_print_list : ?pp_sep:(formatter -> unit -> unit) ->
(formatter -> 'a -> unit) -> formatter -> 'a list -> unit

pp_print_list ?pp_sep pp_v ppf l 打印列表 l 中的项目,使用 pp_v 打印每个项目,并在项目之间调用 pp_seppp_sep 默认为 Format.pp_print_cut)。对空列表不执行任何操作。

val pp_print_array : ?pp_sep:(formatter -> unit -> unit) ->
(formatter -> 'a -> unit) -> formatter -> 'a array -> unit

pp_print_array ?pp_sep pp_v ppf a 打印数组 a 中的项目,使用 pp_v 打印每个项目,并在项目之间调用 pp_seppp_sep 默认为 Format.pp_print_cut)。对空数组不执行任何操作。

如果在调用 pp_print_array 之后对 a 进行变异,则打印的值可能与预期不符,因为 Format 可以延迟打印。可以通过刷新 ppf 来避免这种情况。

val pp_print_seq : ?pp_sep:(formatter -> unit -> unit) ->
(formatter -> 'a -> unit) ->
formatter -> 'a Seq.t -> unit

pp_print_seq ?pp_sep pp_v ppf s 打印序列 s 中的项目,使用 pp_v 打印每个项目,并在项目之间调用 pp_seppp_sep 默认为 Format.pp_print_cut。对空序列不执行任何操作。

此函数不会在无限序列上终止。

val pp_print_text : formatter -> string -> unit

pp_print_text ppf s 使用 Format.pp_print_spaceFormat.pp_force_newline 分别打印空格和换行符来打印 s

val pp_print_option : ?none:(formatter -> unit -> unit) ->
(formatter -> 'a -> unit) -> formatter -> 'a option -> unit

pp_print_option ?none pp_v ppf o 使用 pp_vppf 上打印 o,如果 oSome v,则使用 none,如果 oNonenone 默认情况下不打印任何内容。

val pp_print_result : ok:(formatter -> 'a -> unit) ->
error:(formatter -> 'e -> unit) ->
formatter -> ('a, 'e) result -> unit

pp_print_result ~ok ~error ppf r 使用 okppf 上打印 r,如果 rOk _,则使用 error,如果 rError _

val pp_print_either : left:(formatter -> 'a -> unit) ->
right:(formatter -> 'b -> unit) ->
formatter -> ('a, 'b) Either.t -> unit

pp_print_either ~left ~right ppf e 使用 leftppf 上打印 e,如果 eEither.Left _,则使用 right,如果 eEither.Right _

格式化美观打印

模块 Format 提供了一套完整的 printf 类函数,用于使用格式字符串规范进行漂亮打印。

可以在格式字符串中添加特定注释,以向漂亮打印引擎提供漂亮打印命令。

这些注释使用 @ 字符在格式字符串中引入。例如, 表示空格断开,@, 表示切割,@[ 打开一个新框,@] 关闭最后一个打开的框。

val fprintf : formatter -> ('a, formatter, unit) format -> 'a

fprintf ff fmt arg1 ... argN 根据格式字符串 fmt 格式化参数 arg1argN,并将生成的字符串输出到格式化程序 ff 上。

格式字符串 fmt 是一个包含三种类型对象的字符字符串:普通字符、Printf 模块中指定的转换说明符,以及特定于 Format 模块的漂亮打印指示。

漂亮打印指示字符由 @ 字符引入,其含义如下:

注意:为了防止将 @ 字符解释为漂亮打印指示,请使用 % 字符对其进行转义。旧的引号模式 @@ 已过时,因为它与字符 '@' 的格式化输入解释不兼容。

示例:printf "@[%s@ %d@]@." "x =" 1 等效于 open_box (); print_string "x ="; print_space ();
   print_int 1; close_box (); print_newline ()
。它在漂亮打印的 '水平或垂直' 框内打印 x = 1

val printf : ('a, formatter, unit) format -> 'a

与上面的 fprintf 相同,但输出到 get_std_formatter ()

它的定义类似于 fun fmt -> fprintf (get_std_formatter ()) fmt,但会延迟调用 get_std_formatter,直到收到 format 所需的最后一个参数为止。当与多个域一起使用时,来自这些域的输出将在格式化器被刷新的点(例如,使用 Format.print_flush)相互交织。

val eprintf : ('a, formatter, unit) format -> 'a

与上面的 fprintf 相同,但输出到 get_err_formatter ()

它的定义类似于 fun fmt -> fprintf (get_err_formatter ()) fmt,但会延迟调用 get_err_formatter,直到收到 format 所需的最后一个参数为止。当与多个域一起使用时,来自这些域的输出将在格式化器被刷新的点(例如,使用 Format.print_flush)相互交织。

val sprintf : ('a, unit, string) format -> 'a

与上面的 printf 相同,但不是打印到格式化器,而是返回一个包含格式化参数结果的字符串。请注意,漂亮打印器队列在每次调用 sprintf 时都会被刷新。请注意,如果您的格式字符串包含 %a,您应该使用 asprintf

对于多次相关调用 sprintf 以在单个字符串上输出材料的情况,您应该考虑使用 fprintf 以及预定义的格式化器 str_formatter,并调用 flush_str_formatter () 以获得最终结果。

或者,您可以使用 Format.fprintf,并使用一个写入您自己缓冲区的格式化器:在漂亮打印结束时刷新格式化器和缓冲区将返回所需的字符串。

val asprintf : ('a, formatter, unit, string) format4 -> 'a

与上面的 printf 相同,但不是打印到格式化器,而是返回一个包含格式化参数结果的字符串。asprintf 的类型足够通用,可以很好地与 %a 转换交互。

val dprintf : ('a, formatter, unit, formatter -> unit) format4 -> 'a

Format.fprintf 相同,除了格式化器是最后一个参数。dprintf "..." a b c 是类型为 formatter -> unit 的函数,可以传递给格式说明符 %t

这可以作为 Format.asprintf 的替代方案,以延迟格式化决策。在格式化上下文中使用 Format.asprintf 返回的字符串会强制格式化决策在隔离状态下进行,最终字符串可能过早创建。Format.dprintf 允许将格式化决策延迟到最终格式化上下文已知为止。例如

  let t = Format.dprintf "%i@ %i@ %i" 1 2 3 in
  ...
  Format.printf "@[<v>%t@]" t
val ifprintf : formatter -> ('a, formatter, unit) format -> 'a

与上面的 fprintf 相同,但不会打印任何内容。当条件打印时,这对于忽略某些材料很有用。

带有继续的格式化漂亮打印。

val kfprintf : (formatter -> 'a) ->
formatter -> ('b, formatter, unit, 'a) format4 -> 'b

与上面的 fprintf 相同,但不是立即返回,而是将格式化器传递给打印结束时的第一个参数。

val kdprintf : ((formatter -> unit) -> 'a) ->
('b, formatter, unit, 'a) format4 -> 'b

与上面的 Format.dprintf 相同,但不是立即返回,而是将挂起的打印机传递给打印结束时的第一个参数。

val ikfprintf : (formatter -> 'a) ->
formatter -> ('b, formatter, unit, 'a) format4 -> 'b

与上面的 kfprintf 相同,但不会打印任何内容。当条件打印时,这对于忽略某些材料很有用。

val ksprintf : (string -> 'a) -> ('b, unit, string, 'a) format4 -> 'b

与上面的 sprintf 相同,但不是返回字符串,而是将其传递给第一个参数。

val kasprintf : (string -> 'a) -> ('b, formatter, unit, 'a) format4 -> 'b

与上面的 asprintf 相同,但不是返回字符串,而是将其传递给第一个参数。

示例

一些预热示例,以了解如何使用 Format。

我们有一个 l 对列表,它们是 (int * bool),顶层为我们打印这些对

# let l = List.init 20 (fun n -> n, n mod 2 = 0)
  val l : (int * bool) list =
  [(0, true); (1, false); (2, true); (3, false); (4, true); (5, false);
   (6, true); (7, false); (8, true); (9, false); (10, true); (11, false);
   (12, true); (13, false); (14, true); (15, false); (16, true); (17, false);
   (18, true); (19, false)]
 

如果我们想在没有顶层魔法的情况下自己打印它,我们可以尝试这样做

  # let pp_pair out (x,y) = Format.fprintf out "(%d, %b)" x y
  val pp_pair : Format.formatter -> int * bool -> unit = <fun>
  # Format.printf "l: [@[<hov>%a@]]@."
    Format.(pp_print_list ~pp_sep:(fun out () -> fprintf out ";@ ") pp_pair) l
    l: [(0, true); (1, false); (2, true); (3, false); (4, true); (5, false);
        (6, true); (7, false); (8, true); (9, false); (10, true); (11, false);
        (12, true); (13, false); (14, true); (15, false); (16, true);
        (17, false); (18, true); (19, false)]

  

简而言之,它的作用是

如果我们省略 "@ ",我们会得到一个丑陋的单行打印

# Format.printf "l: [@[<hov>%a@]]@."
      Format.(pp_print_list ~pp_sep:(fun out () -> fprintf out "; ") pp_pair) l
  l: [(0, true); (1, false); (2, true); (* ... *); (18, true); (19, false)]
- : unit = ()
    

通常,为程序中的重要类型定义自定义打印机是一个好习惯。例如,如果您要这样定义基本几何类型

  type point = {
    x: float;
    y: float;
  }

  type rectangle = {
    ll: point; (* lower left *)
    ur: point; (* upper right *)
  }
  

为了调试目的,或在日志或控制台中显示信息,为这些类型定义打印机将很方便。这是一个示例。请注意,"%.3f" 是一个最多精确到小数点后三位的小数打印机;"%f" 会打印所有必需的位数,这有点冗长;"%h" 是一个十六进制浮点数打印机。

  let pp_point out (p:point) =
    Format.fprintf out "{ @[x=%.3f;@ y=%.3f@] }" p.x p.y

  let pp_rectangle out (r:rectangle) =
    Format.fprintf out "{ @[ll=%a;@ ur=%a@] }"
      pp_point r.ll pp_point r.ur
  

.mli 文件中,我们可以有

    val pp_point : Format.formatter -> point -> unit

    val pp_rectangle : Format.formatter -> rectangle -> unit
  

这些打印机现在可以在其他打印机内部使用 "%a"。

 # Format.printf "some rectangle: %a@."
        (Format.pp_print_option pp_rectangle)
        (Some {ll={x=1.; y=2.}; ur={x=42.; y=500.12345}})
  some rectangle: { l={ x=1.000; y=2.000 }; ur={ x=42.000; y=500.123 } }

  # Format.printf "no rectangle: %a@."
        (Format.pp_option pp_rectangle)
        None
  no rectangle:
  

让我们看看如何将 pp_print_option(选项打印器)与我们新定义的矩形打印器结合起来,就像我们之前使用 pp_print_list 一样。

更多详细的教程,请参阅 "使用 Format 模块"

最后说明:Format 模块是一个起点。OCaml 生态系统中有一些库可以使格式化更轻松、更具表现力,包含更多组合器,更简洁的名称等。例如 Fmt 库。

还可以使用 https://github.com/ocaml-ppx/ppx_deriving 或类似的 ppx 衍生器,从类型定义自动生成美化打印器。