Spec-Zone.ru › OCaml

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 (), который очищает таблицы форматировщика (следовательно, вывод). (Обратите внимание, что цикл верхнего уровня интерактивной системы также делает это, непосредственно перед новым вводом.)

Вывод в stdout с использованием 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-2024 INRIA.
https://ocaml.org/manual/5.2/api/Format_tutorial.html

Spec-Zone.ru

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