模块 Stdlib.Format

module Format: Format

简介

您可以将此模块视为对 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 () 发出“剪切”断行提示:漂亮打印程序可能会在此处分割行,否则它不会打印任何内容。

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 发出“完整”断行提示:漂亮打印程序可能会在此处分割行,否则它会打印 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 ()发出“下一个”制表符换行提示:如果尚未设置在制表符标记上,则插入点将移动到右侧的第一个制表符标记,或者美化打印程序将拆分行,并将插入点移动到最左侧的制表符标记。

它等同于print_tbreak 0 0

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

print_tbreak nspaces offset发出“完整”制表符换行提示。

如果尚未设置在制表符标记上,则插入点将移动到右侧的第一个制表符标记,并且美化打印程序将打印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 Format.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 作为参数;它应该输出 spp + 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 作为参数;它应该输出 spp + n - 1 之间的字符。
  • 函数 out_flush 刷新美化打印输出设备。
  • out_newline 在美化打印换行时被调用以打开新行。
  • 当换行提示导致空格而不是换行符时,函数 out_spaces 输出空格。它以要输出的空格数作为参数。
  • 当美化打印换行时,函数 out_indent 执行新行缩进。它以新行的缩进值作为参数。

默认情况下

  • 字段 out_stringout_flush 是特定于输出设备的;(例如,对于 out_channel 设备,是 output_stringflush,或者对于 Buffer.t 输出设备,是 Buffer.add_substringignore),
  • 字段 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_funsppf 上打印时,将打开和关闭语义标签操作的含义更改为使用 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 vppf 上格式化 iter 对值集合 v 的迭代,使用 pp_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,如果它为 None。默认情况下,none 不打印任何内容。

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以在单个字符串上输出内容,则应考虑使用预定义格式化程序str_formatterfprintf,并调用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”是一个小数点后最多3位的float打印机;"%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派生器来自动从类型定义派生漂亮打印机。