換行是基於三個概念
bh
在盒子 b
內觸發新行,則新行的縮排就是:盒子 b
的目前縮排 + 盒子 b
定義的額外盒子斷行縮排 + 斷行提示 bh
定義的額外提示斷行縮排。總共有 4 種盒子類型。(最常用的是 "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
的方便縮寫,會輸出單個空格或斷行。)
使用者有 2 種方式來修正新行的縮排
在定義盒子時:當您開啟盒子時,您可以修正新增到該盒子內開啟的每個新行的縮排。
例如: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 ;;
我使用格式函式庫來列印 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
的程序僅僅是局部應用(partial application)的問題
let print_lambda = pr_lambda std_formatter;; let eprint_lambda = pr_lambda err_formatter;; val print_lambda : lambda -> unit val eprint_lambda : lambda -> unit