Spec-Zone.ru › OCaml 5.0

Format_tutorial

Использование модуля Format

Принципы

Перевод строк основан на трёх концепциях:

  • блоки : блок — это логическая единица красивого форматирования, которая определяет поведение движка красивого форматирования для отображения материала внутри блока.
  • подсказки переноса: подсказка переноса — это директива для движка красивого форматирования, которая предлагает перенос строки здесь, если это необходимо для правильного вывода остальной части материала. В противном случае движок красивого форматирования никогда не переносит строки (кроме «случая крайней необходимости», чтобы избежать очень плохого вывода). Короче говоря, подсказка переноса сообщает красивому форматировщику, что перенос строки здесь может быть уместным.
  • правила отступов: Когда происходит перенос строки, движок красивого форматирования корректирует отступ (или количество ведущих пробелов) новой строки, используя правила отступов, следующим образом:
    • Блок может указывать дополнительный отступ каждой новой строки, открытой в его области. Этот дополнительный отступ называется отступом переноса блока.
    • Подсказка переноса также может задать дополнительный отступ новой строки, которую она может инициировать. Этот дополнительный отступ называется отступом переноса подсказки.
    • Если подсказка переноса bh инициирует новую строку внутри блока b, то отступ новой строки просто является суммой: текущего отступа блока b + дополнительного отступа переноса блока, определённого блоком b + дополнительного отступа переноса подсказки, определённого подсказкой переноса bh.

Блоки

Существует 4 типа блоков. (Чаще всего используется тип блока «hov», поэтому пропустите остальные при первом чтении).

  • горизонтальный блок (h блок, полученный процедурой Format.open_hbox): внутри этого блока подсказки переноса не приводят к переносу строк.
  • вертикальный блок (v блок, полученный процедурой Format.open_vbox): внутри этого блока каждая подсказка переноса приводит к новой строке.
  • вертикально-горизонтальный блок (hv блок, полученный процедурой Format.open_hvbox): если возможно, весь блок записывается в одной строке; в противном случае каждая подсказка переноса внутри блока приводит к новой строке.
  • вертикальный или горизонтальный блок (hov блок, полученный процедурами 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 — это break 1 0 в выводе "--b--b--", мы получаем

внутри блока "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 используется для настройки дополнительного отступа новой строки. То есть оно добавляется к стандартному смещению отступа блока, где происходит перенос.

Например, если [ обозначает открытие блока «hov» с дополнительным отступом 1 (полученным с помощью open_hovbox 1), а b — print_break 1 2, то из вывода "---[--b--b--b--" мы получаем:

   ---[-- --
         --
         --

Уточнения для блоков "hov"

Тип блока «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)]" выводится так:

(---
 (----
  (---
  )
 )
)

Практические советы

При написании процедуры красивого форматирования следуйте этим простым правилам:

  1. Блоки должны открываться и закрываться последовательно (open_* и Format.close_box должны быть вложены как скобки).
  2. Не стесняйтесь открывать блоки.
  3. Выводите много подсказок переноса, в противном случае форматировщик окажется в плохом положении, пытаясь сделать всё возможное, что всегда «хуже, чем плохо».
  4. Не пытайтесь принудительно задать интервалы, используя явные пробелы в строках символов. Для каждого пробела, который вы хотите в выводе, отправляйте подсказку переноса (print_space ()), если только вы явно не хотите, чтобы строка разбивалась здесь. Например, представьте, что вы хотите красиво отформатировать определение OCaml, точнее определение значения let rec
    ident = expression
    . Вы, вероятно, обработаете первые три пробела как «неразрывные пробелы» и напишете их непосредственно в строковые константы для ключевых слов, и выведете "let rec" перед идентификатором, и аналогично напишите = для получения неразрывного пробела после идентификатора; напротив, пробел после символа = определённо является подсказкой переноса, так как разбиение строки после = является обычным (и элегантным) способом отступа части выражения определения. Короче говоря, часто необходимо выводить неразрывные пробелы; однако большинство времени пробел следует рассматривать как подсказку переноса.
  5. Не пытайтесь принудительно создавать новые строки, позвольте форматировщику сделать это за вас: это его единственная задача. В частности, не используйте Format.force_newline: эта процедура эффективно приводит к новой строке, но также имеет нежелательное побочное действие частичного перезапуска движка красивого форматирования, так что остальная часть выводимого материала заметно нарушается.
  6. Никогда не вставляйте символы новой строки непосредственно в строки, которые будут выведены: движок красивого форматирования будет рассматривать этот символ новой строки как любой другой символ, написанный в текущей строке, и это полностью испортит вывод. Вместо символов новой строки используйте подсказки переноса строк: если эти подсказки переноса строк всегда должны приводить к новым строкам, это просто означает, что окружающий блок должен быть вертикальным блоком!
  7. Завершите свою основную программу вызовом print_newline (), который очищает таблицы форматировщика (а следовательно, и вывод). (Обратите внимание, что верхний цикл интерактивной системы также делает это, сразу перед новым вводом.)

Вывод в стандартный вывод: использование printf

Модуль format предоставляет общую функцию вывода «по типу» 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 = ()

Конкретный пример

Давайте приведу полный пример: самый короткий нетривиальный пример, который вы можете себе представить, это лямбда-исчисление :)

Таким образом, задача заключается в красивой печати значений конкретного типа данных, который моделирует язык выражений, определяющих функции и их применения к аргументам.

Во-первых, я приведу абстрактную синтаксическую структуру лямбда-термов:

type lambda =
 | Lambda of string * lambda
 | Var of string
 | Apply of 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

Мы используем функцию fprintf для написания наиболее универсальной версии функций красивой печати для лямбда-термов. Теперь функции получают дополнительный аргумент, а именно форматировщик красивой печати (аргумент ppf), где будет происходить печать. Таким образом, функции печати более общие, поскольку они могут печатать в любой форматировщик, определенный в программе (либо печатать в файл, либо в stdout, в stderr, или даже в строку). Кроме того, функции красивой печати теперь композиционные, поскольку они могут использоваться совместно со специальной конверсией %a, которая печатает аргумент fprintf с функцией, предоставленной пользователем (эти функции, предоставленные пользователем, также имеют форматировщик в качестве первого аргумента).

Используя fprintf, функции печати лямбда-термов могут быть написаны следующим образом:

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

© 1995-2022 INRIA.
https://v2.ocaml.org/releases/5.0/htmlman/libref/Format_tutorial.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API