├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .ocamlformat ├── CHANGES.md ├── LICENSE.md ├── Makefile ├── README.md ├── dune-project ├── pp.opam ├── src ├── dune ├── pp.ml └── pp.mli └── test ├── dune └── tests.ml /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | build: 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | os: 13 | - macos-latest 14 | - ubuntu-latest 15 | - windows-latest 16 | ocaml-compiler: 17 | - 5.2.x 18 | - 4.14.x 19 | - 4.13.x 20 | include: 21 | # OCaml 4.12.x: skipping windows (fail) 22 | - ocaml-compiler: 4.12.x 23 | os: ubuntu-latest 24 | - ocaml-compiler: 4.12.x 25 | os: macos-latest 26 | # OCaml 4.11.x: skipping macos & windows (fail) 27 | - ocaml-compiler: 4.11.x 28 | os: ubuntu-latest 29 | # OCaml 4.10.x: skipping windows (fail) 30 | - ocaml-compiler: 4.10.x 31 | os: ubuntu-latest 32 | - ocaml-compiler: 4.10.x 33 | os: macos-latest 34 | # OCaml 4.08.x: skipping macos & windows (fail) 35 | # Keep the minimal version of OCaml in sync in here and `dune-project` 36 | - ocaml-compiler: 4.08.x 37 | os: ubuntu-latest 38 | 39 | runs-on: ${{ matrix.os }} 40 | 41 | steps: 42 | - name: Set git to use LF 43 | run: | 44 | git config --global core.autocrlf false 45 | git config --global core.eol lf 46 | git config --global core.ignorecase false 47 | 48 | - name: Checkout code 49 | uses: actions/checkout@v4 50 | 51 | - name: Use OCaml ${{ matrix.ocaml-compiler }} 52 | uses: ocaml/setup-ocaml@v3 53 | with: 54 | ocaml-compiler: ${{ matrix.ocaml-compiler }} 55 | 56 | - name: Install dependencies 57 | run: opam install . --deps-only --with-doc --with-test --with-dev-setup 58 | 59 | - name: Build 60 | run: opam exec -- dune build 61 | 62 | - name: Run tests 63 | run: opam exec -- dune runtest 64 | 65 | - name: Check for uncommitted changes 66 | run: git diff --exit-code 67 | 68 | - name: Lint fmt 69 | uses: ocaml/setup-ocaml/lint-fmt@v3 70 | 71 | - name: Lint doc 72 | uses: ocaml/setup-ocaml/lint-doc@v3 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _opam 2 | _build 3 | *.install 4 | .merlin 5 | -------------------------------------------------------------------------------- /.ocamlformat: -------------------------------------------------------------------------------- 1 | version=0.26.2 2 | break-separators=before 3 | dock-collection-brackets=false 4 | break-sequences=true 5 | doc-comments=before 6 | field-space=loose 7 | let-and=sparse 8 | sequence-style=terminator 9 | type-decl=sparse 10 | wrap-comments=true 11 | if-then-else=k-r 12 | let-and=sparse 13 | space-around-records 14 | space-around-lists 15 | space-around-arrays 16 | cases-exp-indent=2 17 | break-cases=all 18 | indicate-nested-or-patterns=unsafe-no 19 | parse-docstrings=true 20 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | 2.0.0 2 | ----- 3 | 4 | - Prepare release (#21, @mbarbin) 5 | - Upgrade to `ocamlformat.0.26.2`. 6 | - Fmt the code 7 | - Add CI badge to README 8 | - Upgrade GitHub workflow actions dependencies (checkout@v4, setup-ocaml@v3) 9 | - Add more validation steps in CI 10 | - Add `ocamlformat` as dev-setup dependency 11 | 12 | - Add `Pp.verbatimf`. (#18, @mbarbin) 13 | 14 | - Add `Pp.paragraph` and `Pp.paragraphf` (#19, @Alizter) 15 | 16 | - Remove `of_fmt` constructor. (#17, @Alizter) 17 | 18 | 1.2.0 19 | ----- 20 | 21 | - Add `Pp.compare` (#9, @Alizter) 22 | 23 | 1.1.2 24 | ----- 25 | 26 | - Add `of_fmt` to compose with existing pretty printers written in `Format` 27 | (#1, @Drup). 28 | 29 | - Use a tail-recursive `List.map` to fix a stack overflow issue (#3, 30 | @emillon) 31 | 32 | - Add `Pp.custom_break` (#4, @gpetiot) 33 | 34 | - Add `Ast` sub-module to expose a stable representation for 35 | serialization, allowing to do the rendering in another process (#6, 36 | @rgrinberg) 37 | 38 | 1.1.1 39 | ----- 40 | 41 | Replaced by 1.1.2 because of wrong URLs in opam file. 42 | 43 | 1.1.0 44 | ----- 45 | 46 | Replaced by 1.1.1 because of missing changelog entries. 47 | 48 | 1.0.1 49 | ----- 50 | 51 | - Fix compat with OCaml 4.04 52 | 53 | 1.0.0 54 | ----- 55 | 56 | - Initial release 57 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2016 Jane Street Group, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | INSTALL_ARGS := $(if $(PREFIX),--prefix $(PREFIX),) 2 | 3 | default: 4 | dune build 5 | 6 | test: 7 | dune runtest 8 | 9 | install: 10 | dune install $(INSTALL_ARGS) 11 | 12 | uninstall: 13 | dune uninstall $(INSTALL_ARGS) 14 | 15 | reinstall: uninstall install 16 | 17 | clean: 18 | dune clean 19 | 20 | release: 21 | dune-release tag 22 | dune-release distrib --skip-build --skip-lint --skip-tests -n pp 23 | # See https://github.com/ocamllabs/dune-release/issues/206 24 | DUNE_RELEASE_DELEGATE=github-dune-release-delegate dune-release publish distrib --verbose -n pp 25 | dune-release opam pkg -n pp 26 | dune-release opam submit -n pp 27 | 28 | .PHONY: default install uninstall reinstall clean test 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pp - Pretty printing 2 | ==================== 3 | 4 | [![CI Status](https://github.com/ocaml-dune/pp/workflows/CI/badge.svg)](https://github.com/ocaml-dune/pp/actions/workflows/ci.yml) 5 | 6 | This library provides a lean alternative to the [Format][format] 7 | module of the OCaml standard library. It aims to make it easy for 8 | users to do the right thing. If you have tried `Format` before but 9 | find its API complicated and difficult to use, then `Pp` might be a 10 | good choice for you. 11 | 12 | `Pp` uses the same concepts of boxes and break hints, and the final 13 | rendering is done to formatter from the `Format` module. However it 14 | defines its own algebra which I personaly find easier to work with and 15 | reason about. No previous knowledge is required to start using this 16 | library, however the various guides for the `Format` module such as 17 | [this one][format-guide] should be applicable to `Pp` as well. 18 | 19 | Examples 20 | -------- 21 | 22 | ```ocaml 23 | # #require "pp";; 24 | # let print pp = Format.printf "%a@." Pp.to_fmt pp;; 25 | val print : 'a Pp.t -> unit = 26 | # print (Pp.enumerate (List.init 10 Fun.id) ~f:(Pp.textf "%d"));; 27 | - 0 28 | - 1 29 | - 2 30 | - 3 31 | - 4 32 | - 5 33 | - 6 34 | - 7 35 | - 8 36 | - 9 37 | # print (Pp.box ~indent:2 (Pp.text 38 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed \ 39 | do eiusmod tempor incididunt ut labore et dolore magna \ 40 | aliqua. Ut enim ad minim veniam, quis nostrud exercitation \ 41 | ullamco laboris nisi ut aliquip ex ea commodo \ 42 | consequat. Duis aute irure dolor in reprehenderit in \ 43 | voluptate velit esse cillum dolore eu fugiat nulla \ 44 | pariatur. Excepteur sint occaecat cupidatat non proident, \ 45 | sunt in culpa qui officia deserunt mollit anim id est \ 46 | laborum."));; 47 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor 48 | incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis 49 | nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. 50 | Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore 51 | eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, 52 | sunt in culpa qui officia deserunt mollit anim id est laborum. 53 | - : unit = () 54 | # print 55 | (Pp.vbox 56 | ( Pp.box (Pp.text "Error: something went wrong!") 57 | ++ Pp.cut 58 | ++ Pp.box (Pp.text "Here are a few things you can do:") 59 | ++ Pp.cut 60 | ++ Pp.enumerate ~f:Fun.id 61 | [ Pp.text 62 | "read the documentation, double check the way you are using \ 63 | this software to make sure you are not doing something wrong, \ 64 | and hopefully fix the problem on your side and move on" 65 | ; Pp.text 66 | "strace furiously the program to try and understand why \ 67 | exactly it is trying to do what it is doing" 68 | ; Pp.text "report an issue upstream" 69 | ; Pp.text "if all else fails" 70 | ++ Pp.cut 71 | ++ Pp.enumerate ~f:Pp.text 72 | [ "scream loudly at your computer" 73 | ; "take a break from your keyboard" 74 | ; "clear your head and try again" 75 | ] 76 | ] ));; 77 | Error: something went wrong! 78 | Here are a few things you can do: 79 | - read the documentation, double check the way you are using this software to 80 | make sure you are not doing something wrong, and hopefully fix the problem on 81 | your side and move on 82 | - strace furiously the program to try and understand why exactly it is trying 83 | to do what it is doing 84 | - report an issue upstream 85 | - if all else fails 86 | - scream loudly at your computer 87 | - take a break from your keyboard 88 | - clear your head and try again 89 | - : unit = () 90 | ``` 91 | 92 | Resources 93 | --------- 94 | 95 | As mentioned earlier, [this Format guide][format-guide] can be a good 96 | starting point to understand the pretty-printing mechanics of `Pp`. 97 | Additionally, [Format Unraveled][format-unraveled] is a great resource 98 | for understanding the core mental model of `Format`. And since `Pp` 99 | uses the same concepts as `Format`, it can be a good resource for `Pp` 100 | too. 101 | 102 | Note that the Format Unraveled paper discuss some limitations of 103 | `Format` that are due to the fact that it never has the full document 104 | in-memory before rendering it. This does not apply to `Pp` since `Pp` 105 | clearly always construct the full document in-memory. However, since 106 | right now the only way to render a `Pp.t` is via the `Format` module, 107 | the same limitations that apply to `Format` apply to `Pp` as well. We 108 | might add another renderer in the future that does not have these 109 | limitations if there is sufficient incentive to do so. 110 | 111 | History 112 | ------- 113 | 114 | This library comes from the [dune build system][dune]. Initially, to 115 | construct the various messages displayed to the user on the terminal, 116 | dune was mostly using the `Format` module, and in particular the 117 | `Format.fprintf` style format strings. The `Format` API, its concepts 118 | and printf-like format strings are quite complicated and not easy to 119 | grasp at all. It requires quite a bit of learning and practice before 120 | one can be fluent with `Format`. 121 | 122 | What is more, it is well known that programmers absolutely "love" 123 | spending time writing good error messages. Hint: this is sarcastic. 124 | 125 | The result of all this was terrible and most messages printed by DUne 126 | where badly formatted. So to remedy to the situation we introduced a 127 | `Pp` module in `stdune`, the mini-standard library inside Dune. `Pp` 128 | is completely detached from `Format`, and there is no mention of 129 | `formatter` until the rendering stage. While in the `Pp` world, all we 130 | do is construct a document with various formatting hints. 131 | 132 | In the end, the API of `Pp` just makes it easy for someone to do the 133 | right thing. And this makes all the difference. Since then, it has 134 | been easy to construct well formatted error messages for Dune and the 135 | formatting of existing error messages has generally improved. 136 | 137 | Once `Pp` was mature enough, we extracted it into its own library so 138 | that it can benefit others. 139 | 140 | Interoperability 141 | ---------------- 142 | 143 | It is easy to integrate `Pp` with `Format`. For that, simply use the 144 | `Pp.to_fmt` function. For instance, if you have a value `pp` of type 145 | `_ Pp.t` you can do: 146 | 147 | ```ocaml 148 | Format.fprintf "... %a ..." Pp.to_fmt pp 149 | ``` 150 | 151 | If you are familiar with the [fmt library][fmt], `Pp.to_fmt` basically 152 | allows you to go from a `'a Pp.t` to a `'a Fmt.t`. The opposite is not 153 | possible; it is not possible to inject arbitrary side-effecting 154 | formatting functions into a `Pp.t`. 155 | 156 | If you want to convert `Pp` tags fot `Format` tags, you can use the 157 | function `Pp.to_fmt_with_tags`. 158 | 159 | Comparison with other libraries 160 | ------------------------------- 161 | 162 | This is not an in-depth comparison as I haven't used these libraries 163 | much myself, so this is to be taken with a grain of salt. The below is 164 | basically what I can tell from a quick look at their API. If you know 165 | more and would like to contribute to this comparison, please do so by 166 | opening a PR :) 167 | 168 | ### Comparison with fmt 169 | 170 | The main difference with [fmt][fmt] is that `Fmt.t` is am alias for 171 | `Format.formatter -> 'a -> unit`, while `Pp.t` is an abstract type. 172 | 173 | ### Comparison with easy-format 174 | 175 | The [easy-format library][easy-format] looks much higher-level than 176 | `Pp`. `Pp` still works with boxes and break hints like the `Format` 177 | module, while `easy-format` works with atoms, lists and labelled 178 | nodes. 179 | 180 | [format]: https://caml.inria.fr/pub/docs/manual-ocaml/libref/Format.html 181 | [format-unraveled]: https://hal.archives-ouvertes.fr/hal-01503081/file/format-unraveled.pdf 182 | [dune]: https://dune.build 183 | [fmt]: https://erratique.ch/software/fmt 184 | [format-guide]: http://caml.inria.fr/resources/doc/guides/format.en.html 185 | [easy-format]: https://github.com/mjambon/easy-format 186 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.8) 2 | (name pp) 3 | 4 | (license MIT) 5 | (maintainers "Jeremie Dimino ") 6 | (authors 7 | "Jane Street Group, LLC " 8 | "Jeremie Dimino ") 9 | (source (github ocaml-dune/pp)) 10 | (documentation "https://ocaml-dune.github.io/pp/") 11 | 12 | (generate_opam_files true) 13 | 14 | (package 15 | (name pp) 16 | (depends 17 | (ocaml (>= 4.08)) 18 | (ppx_expect :with-test) 19 | (ocamlformat 20 | (and 21 | :with-dev-setup 22 | (= 0.26.2)))) 23 | (synopsis "Pretty-printing library") 24 | (description " 25 | This library provides a lean alternative to the Format [1] module of 26 | the OCaml standard library. It aims to make it easy for users to do 27 | the right thing. If you have tried Format before but find its API 28 | complicated and difficult to use, then Pp might be a good choice for 29 | you. 30 | 31 | Pp uses the same concepts of boxes and break hints, and the final 32 | rendering is done to formatter from the Format module. However it 33 | defines its own algebra which some might find easier to work with and 34 | reason about. No previous knowledge is required to start using this 35 | library, however the various guides for the Format module such as this 36 | one [2] should be applicable to Pp as well. 37 | 38 | [1]: https://caml.inria.fr/pub/docs/manual-ocaml/libref/Format.html 39 | [2]: http://caml.inria.fr/resources/doc/guides/format.en.html 40 | ")) 41 | -------------------------------------------------------------------------------- /pp.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: "Pretty-printing library" 4 | description: """ 5 | 6 | This library provides a lean alternative to the Format [1] module of 7 | the OCaml standard library. It aims to make it easy for users to do 8 | the right thing. If you have tried Format before but find its API 9 | complicated and difficult to use, then Pp might be a good choice for 10 | you. 11 | 12 | Pp uses the same concepts of boxes and break hints, and the final 13 | rendering is done to formatter from the Format module. However it 14 | defines its own algebra which some might find easier to work with and 15 | reason about. No previous knowledge is required to start using this 16 | library, however the various guides for the Format module such as this 17 | one [2] should be applicable to Pp as well. 18 | 19 | [1]: https://caml.inria.fr/pub/docs/manual-ocaml/libref/Format.html 20 | [2]: http://caml.inria.fr/resources/doc/guides/format.en.html 21 | """ 22 | maintainer: ["Jeremie Dimino "] 23 | authors: [ 24 | "Jane Street Group, LLC " 25 | "Jeremie Dimino " 26 | ] 27 | license: "MIT" 28 | homepage: "https://github.com/ocaml-dune/pp" 29 | doc: "https://ocaml-dune.github.io/pp/" 30 | bug-reports: "https://github.com/ocaml-dune/pp/issues" 31 | depends: [ 32 | "dune" {>= "2.8"} 33 | "ocaml" {>= "4.08"} 34 | "ppx_expect" {with-test} 35 | "ocamlformat" {with-dev-setup & = "0.26.2"} 36 | "odoc" {with-doc} 37 | ] 38 | build: [ 39 | ["dune" "subst"] {dev} 40 | [ 41 | "dune" 42 | "build" 43 | "-p" 44 | name 45 | "-j" 46 | jobs 47 | "@install" 48 | "@runtest" {with-test} 49 | "@doc" {with-doc} 50 | ] 51 | ] 52 | dev-repo: "git+https://github.com/ocaml-dune/pp.git" 53 | -------------------------------------------------------------------------------- /src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (public_name pp)) 3 | -------------------------------------------------------------------------------- /src/pp.ml: -------------------------------------------------------------------------------- 1 | module List = struct 2 | include ListLabels 3 | 4 | let map ~f t = rev (rev_map ~f t) 5 | end 6 | 7 | module String = StringLabels 8 | 9 | module Ast = struct 10 | type +'a t = 11 | | Nop 12 | | Seq of 'a t * 'a t 13 | | Concat of 'a t * 'a t list 14 | | Box of int * 'a t 15 | | Vbox of int * 'a t 16 | | Hbox of 'a t 17 | | Hvbox of int * 'a t 18 | | Hovbox of int * 'a t 19 | | Verbatim of string 20 | | Char of char 21 | | Break of (string * int * string) * (string * int * string) 22 | | Newline 23 | | Text of string 24 | | Tag of 'a * 'a t 25 | end 26 | 27 | include Ast 28 | 29 | let of_ast = Fun.id 30 | let to_ast = Fun.id 31 | 32 | type ('a, 'tag) format_string = ('a, unit, string, 'tag t) format4 33 | 34 | let rec map_tags t ~f = 35 | match t with 36 | | Nop -> Nop 37 | | Seq (a, b) -> Seq (map_tags a ~f, map_tags b ~f) 38 | | Concat (sep, l) -> Concat (map_tags sep ~f, List.map l ~f:(map_tags ~f)) 39 | | Box (indent, t) -> Box (indent, map_tags t ~f) 40 | | Vbox (indent, t) -> Vbox (indent, map_tags t ~f) 41 | | Hbox t -> Hbox (map_tags t ~f) 42 | | Hvbox (indent, t) -> Hvbox (indent, map_tags t ~f) 43 | | Hovbox (indent, t) -> Hovbox (indent, map_tags t ~f) 44 | | (Verbatim _ | Char _ | Break _ | Newline | Text _) as t -> t 45 | | Tag (tag, t) -> Tag (f tag, map_tags t ~f) 46 | 47 | let rec filter_map_tags t ~f = 48 | match t with 49 | | Nop -> Nop 50 | | Seq (a, b) -> Seq (filter_map_tags a ~f, filter_map_tags b ~f) 51 | | Concat (sep, l) -> 52 | Concat (filter_map_tags sep ~f, List.map l ~f:(filter_map_tags ~f)) 53 | | Box (indent, t) -> Box (indent, filter_map_tags t ~f) 54 | | Vbox (indent, t) -> Vbox (indent, filter_map_tags t ~f) 55 | | Hbox t -> Hbox (filter_map_tags t ~f) 56 | | Hvbox (indent, t) -> Hvbox (indent, filter_map_tags t ~f) 57 | | Hovbox (indent, t) -> Hovbox (indent, filter_map_tags t ~f) 58 | | (Verbatim _ | Char _ | Break _ | Newline | Text _) as t -> t 59 | | Tag (tag, t) -> ( 60 | let t = filter_map_tags t ~f in 61 | match f tag with 62 | | None -> t 63 | | Some tag -> Tag (tag, t)) 64 | 65 | module Render = struct 66 | open Format 67 | 68 | let rec render ppf t ~tag_handler = 69 | match t with 70 | | Nop -> () 71 | | Seq (a, b) -> 72 | render ppf ~tag_handler a; 73 | render ppf ~tag_handler b 74 | | Concat (_, []) -> () 75 | | Concat (sep, x :: l) -> 76 | render ppf ~tag_handler x; 77 | List.iter l ~f:(fun x -> 78 | render ppf ~tag_handler sep; 79 | render ppf ~tag_handler x) 80 | | Box (indent, t) -> 81 | pp_open_box ppf indent; 82 | render ppf ~tag_handler t; 83 | pp_close_box ppf () 84 | | Vbox (indent, t) -> 85 | pp_open_vbox ppf indent; 86 | render ppf ~tag_handler t; 87 | pp_close_box ppf () 88 | | Hbox t -> 89 | pp_open_hbox ppf (); 90 | render ppf ~tag_handler t; 91 | pp_close_box ppf () 92 | | Hvbox (indent, t) -> 93 | pp_open_hvbox ppf indent; 94 | render ppf ~tag_handler t; 95 | pp_close_box ppf () 96 | | Hovbox (indent, t) -> 97 | pp_open_hovbox ppf indent; 98 | render ppf ~tag_handler t; 99 | pp_close_box ppf () 100 | | Verbatim x -> pp_print_string ppf x 101 | | Char x -> pp_print_char ppf x 102 | | Break (fits, breaks) -> pp_print_custom_break ppf ~fits ~breaks 103 | | Newline -> pp_force_newline ppf () 104 | | Text s -> pp_print_text ppf s 105 | | Tag (tag, t) -> tag_handler ppf tag t 106 | end 107 | 108 | let to_fmt_with_tags = Render.render 109 | 110 | let rec to_fmt ppf t = 111 | Render.render ppf t ~tag_handler:(fun ppf _tag t -> to_fmt ppf t) 112 | 113 | let nop = Nop 114 | let seq a b = Seq (a, b) 115 | 116 | let concat ?(sep = Nop) = function 117 | | [] -> Nop 118 | | [ x ] -> x 119 | | l -> Concat (sep, l) 120 | 121 | let concat_map ?(sep = Nop) l ~f = 122 | match l with 123 | | [] -> Nop 124 | | [ x ] -> f x 125 | | l -> Concat (sep, List.map l ~f) 126 | 127 | let concat_mapi ?(sep = Nop) l ~f = 128 | match l with 129 | | [] -> Nop 130 | | [ x ] -> f 0 x 131 | | l -> Concat (sep, List.mapi l ~f) 132 | 133 | let box ?(indent = 0) t = Box (indent, t) 134 | let vbox ?(indent = 0) t = Vbox (indent, t) 135 | let hbox t = Hbox t 136 | let hvbox ?(indent = 0) t = Hvbox (indent, t) 137 | let hovbox ?(indent = 0) t = Hovbox (indent, t) 138 | let verbatim x = Verbatim x 139 | let verbatimf fmt = Printf.ksprintf verbatim fmt 140 | let char x = Char x 141 | let custom_break ~fits ~breaks = Break (fits, breaks) 142 | 143 | let break ~nspaces ~shift = 144 | custom_break ~fits:("", nspaces, "") ~breaks:("", shift, "") 145 | 146 | let space = break ~nspaces:1 ~shift:0 147 | let cut = break ~nspaces:0 ~shift:0 148 | let newline = Newline 149 | let text s = Text s 150 | let textf (fmt : ('a, 'tag) format_string) = Printf.ksprintf text fmt 151 | let tag tag t = Tag (tag, t) 152 | let paragraph s = hovbox (text s) 153 | let paragraphf (fmt : ('a, 'tag) format_string) = Printf.ksprintf paragraph fmt 154 | 155 | let enumerate l ~f = 156 | vbox 157 | (concat ~sep:cut 158 | (List.map l ~f:(fun x -> box ~indent:2 (seq (verbatim "- ") (f x))))) 159 | 160 | let chain l ~f = 161 | vbox 162 | (concat ~sep:cut 163 | (List.mapi l ~f:(fun i x -> 164 | box ~indent:3 165 | (seq 166 | (verbatim 167 | (if i = 0 then 168 | " " 169 | else 170 | "-> ")) 171 | (f x))))) 172 | 173 | module O = struct 174 | let ( ++ ) = seq 175 | end 176 | 177 | let compare = 178 | let compare_both (type a b) (f : a -> a -> int) (g : b -> b -> int) (a, b) 179 | (c, d) = 180 | let r = f a c in 181 | if r <> 0 then 182 | r 183 | else 184 | g b d 185 | in 186 | (* Due to 4.08 lower bound, we need to define this here. *) 187 | let rec compare_list a b ~cmp:f : int = 188 | match (a, b) with 189 | | [], [] -> 0 190 | | [], _ :: _ -> -1 191 | | _ :: _, [] -> 1 192 | | x :: a, y :: b -> ( 193 | match (f x y : int) with 194 | | 0 -> compare_list a b ~cmp:f 195 | | ne -> ne) 196 | in 197 | fun compare_tag -> 198 | let rec compare x y = 199 | match (x, y) with 200 | | Nop, Nop -> 0 201 | | Nop, _ -> -1 202 | | _, Nop -> 1 203 | | Seq (a, b), Seq (c, d) -> compare_both compare compare (a, b) (c, d) 204 | | Seq _, _ -> -1 205 | | _, Seq _ -> 1 206 | | Concat (a, b), Concat (c, d) -> 207 | compare_both compare (compare_list ~cmp:compare) (a, b) (c, d) 208 | | Concat _, _ -> -1 209 | | _, Concat _ -> 1 210 | | Box (a, b), Box (c, d) -> compare_both Int.compare compare (a, b) (c, d) 211 | | Box _, _ -> -1 212 | | _, Box _ -> 1 213 | | Vbox (a, b), Vbox (c, d) -> 214 | compare_both Int.compare compare (a, b) (c, d) 215 | | Vbox _, _ -> -1 216 | | _, Vbox _ -> 1 217 | | Hbox a, Hbox b -> compare a b 218 | | Hbox _, _ -> -1 219 | | _, Hbox _ -> 1 220 | | Hvbox (a, b), Hvbox (c, d) -> 221 | compare_both Int.compare compare (a, b) (c, d) 222 | | Hvbox _, _ -> -1 223 | | _, Hvbox _ -> 1 224 | | Hovbox (a, b), Hovbox (c, d) -> 225 | compare_both Int.compare compare (a, b) (c, d) 226 | | Hovbox _, _ -> -1 227 | | _, Hovbox _ -> 1 228 | | Verbatim a, Verbatim b -> String.compare a b 229 | | Verbatim _, _ -> -1 230 | | _, Verbatim _ -> 1 231 | | Char a, Char b -> Char.compare a b 232 | | Char _, _ -> -1 233 | | _, Char _ -> 1 234 | | Break (a, b), Break (c, d) -> 235 | let compare (x, y, z) (a, b, c) = 236 | compare_both String.compare 237 | (compare_both Int.compare String.compare) 238 | (x, (y, z)) 239 | (a, (b, c)) 240 | in 241 | compare_both compare compare (a, b) (c, d) 242 | | Break _, _ -> -1 243 | | _, Break _ -> 1 244 | | Newline, Newline -> 0 245 | | Newline, _ -> -1 246 | | _, Newline -> 1 247 | | Text a, Text b -> String.compare a b 248 | | Text _, _ -> -1 249 | | _, Text _ -> 1 250 | | Tag (a, b), Tag (c, d) -> compare_both compare_tag compare (a, b) (c, d) 251 | in 252 | compare 253 | -------------------------------------------------------------------------------- /src/pp.mli: -------------------------------------------------------------------------------- 1 | (** Pretty-printing. *) 2 | 3 | (** ['tag t] represents a document that is not yet rendered. The argument ['tag] 4 | is the type of tags in the document. For instance tags might be used for 5 | styles. 6 | 7 | If you want to serialise and deserialise this datastructure, you can use the 8 | [Ast.t] type together with the [of_ast] and [to_ast] functions. *) 9 | type +'tag t 10 | 11 | (** {1 Basic combinators} *) 12 | 13 | (** A pretty printer that prints nothing *) 14 | val nop : 'tag t 15 | 16 | (** [seq x y] prints [x] and then [y] *) 17 | val seq : 'tag t -> 'tag t -> 'tag t 18 | 19 | (** [concat ?sep l] prints elements in [l] separated by [sep]. [sep] defaults to 20 | [nop]. *) 21 | val concat : ?sep:'tag t -> 'tag t list -> 'tag t 22 | 23 | (** Convenience function for [List.map] followed by [concat]. *) 24 | val concat_map : ?sep:'tag t -> 'a list -> f:('a -> 'tag t) -> 'tag t 25 | 26 | (** Convenience function for [List.mapi] followed by [concat]. *) 27 | val concat_mapi : ?sep:'tag t -> 'a list -> f:(int -> 'a -> 'tag t) -> 'tag t 28 | 29 | (** An indivisible block of text. *) 30 | val verbatim : string -> 'tag t 31 | 32 | (** Same as [verbatim] but take a format string as argument. *) 33 | val verbatimf : ('a, unit, string, 'tag t) format4 -> 'a 34 | 35 | (** A single character. *) 36 | val char : char -> 'tag t 37 | 38 | (** Print a bunch of text. The line may be broken at any spaces in the text. *) 39 | val text : string -> 'tag t 40 | 41 | (** Same as [text] but take a format string as argument. *) 42 | val textf : ('a, unit, string, 'tag t) format4 -> 'a 43 | 44 | (** {1 Break hints} *) 45 | 46 | (** [space] instructs the pretty-printing algorithm that the line may be broken 47 | at this point. If the algorithm decides not to break the line, a single 48 | space will be printed instead. 49 | 50 | So for instance [verbatim "x" ++ space ++ verbatim "y"] might produce "x y" 51 | or "x\ny". *) 52 | val space : 'tag t 53 | 54 | (** [cut] instructs the pretty-printing algorithm that the line may be broken at 55 | this point. If the algorithm decides not to break the line, nothing is 56 | printed instead. 57 | 58 | So for instance [verbatim "x" ++ cut ++ verbatim "y"] might produce "xy" or 59 | "x\ny". *) 60 | val cut : 'tag t 61 | 62 | (** [break] is a generalisation of [space] and [cut]. It also instructs the 63 | pretty-printing algorithm that the line may be broken at this point. If it 64 | ends up being broken, [shift] will be added to the indentation level, 65 | otherwise [nspaces] spaces will be printed. [shift] can be negative, in 66 | which case the indentation will be reduced. *) 67 | val break : nspaces:int -> shift:int -> 'tag t 68 | 69 | (** [custom_break ~fits:(a, b, c) ~breaks:(x, y, z)] is a generalisation of 70 | [break]. It also instructs the pretty-printing algorithm that the line may 71 | be broken at this point. If it ends up being broken, [x] is printed, the 72 | line breaks, [y] will be added to the indentation level and [z] is printed, 73 | otherwise [a] will be printed, [b] spaces are printed and then [c] is 74 | printed. The indentation [y] can be negative, in which case the indentation 75 | will be reduced. *) 76 | val custom_break : 77 | fits:string * int * string -> breaks:string * int * string -> 'tag t 78 | 79 | (** Force a newline to be printed. Usage is discourage since it breaks printing 80 | with boxes. If you need to add breaks to your text, put your items into 81 | [box]es and [concat] with a separating [space] afterwhich wrapping it in a 82 | [vbox]. *) 83 | val newline : 'tag t 84 | 85 | (** {1 Boxes} *) 86 | 87 | (** Boxes are the basic components to control the layout of the text. Break 88 | hints such as [space] and [cut] may cause the line to be broken, depending 89 | on the splitting rules. Whenever a line is split, the rest of the material 90 | printed in the box is indented with [indent]. 91 | 92 | You can think of a box with indentation as something with this shape: 93 | 94 | {v 95 | ######################### <- first line 96 | ################# 97 | ################# 98 | ################# 99 | ################# 100 | v} 101 | 102 | And the top left corner of this shape is anchored where the box was 103 | declared. So for instance, the following document: 104 | 105 | {[ 106 | Pp.verbatim "....." ++ Pp.box ~indent:2 (Pp.text "some long ... text") 107 | ]} 108 | 109 | would produce: 110 | 111 | {v 112 | .....some long ... 113 | text 114 | v} *) 115 | 116 | (** Try to put as much as possible on each line. Additionally, a break hint 117 | always break the line if the breaking would reduce the indentation level 118 | inside the box ([break] with negative [shift] value). *) 119 | val box : ?indent:int -> 'tag t -> 'tag t 120 | 121 | (** Always break the line when encountering a break hint. *) 122 | val vbox : ?indent:int -> 'tag t -> 'tag t 123 | 124 | (** Print everything on one line, no matter what *) 125 | val hbox : 'tag t -> 'tag t 126 | 127 | (** If possible, print everything on one line. Otherwise, behave as a [vbox] *) 128 | val hvbox : ?indent:int -> 'tag t -> 'tag t 129 | 130 | (** Try to put as much as possible on each line. Basically the same as [box] but 131 | without the rule about breaks with negative [shift] value. *) 132 | val hovbox : ?indent:int -> 'tag t -> 'tag t 133 | 134 | (** {1 Tags} *) 135 | 136 | (** Tags are arbitrary pieces of information attached to a document. They can be 137 | used to add styles to pretty-printed text, for instance to print to the 138 | terminal with colors. *) 139 | 140 | (** [tag x t] Tag the material printed by [t] with [x] *) 141 | val tag : 'tag -> 'tag t -> 'tag t 142 | 143 | (** Convert tags in a documents *) 144 | val map_tags : 'from_tag t -> f:('from_tag -> 'to_tag) -> 'to_tag t 145 | 146 | (** Convert tags in a documents, possibly removing some tags. *) 147 | val filter_map_tags : 148 | 'from_tag t -> f:('from_tag -> 'to_tag option) -> 'to_tag t 149 | 150 | (** {1 Convenience functions} *) 151 | 152 | (** [paragraph s] is [hovbox (text s)]. This is useful to preserve the structure 153 | of a paragraph of text without worrying about it being broken by a [vbox]. *) 154 | val paragraph : string -> 'tag t 155 | 156 | (** [paragraphf s] is [textf s] followed by a [hovbox]. The [textf] version of 157 | [paragraph]. *) 158 | val paragraphf : ('a, unit, string, 'tag t) format4 -> 'a 159 | 160 | (** [enumerate l ~f] produces an enumeration of the form: 161 | 162 | {v 163 | - item1 164 | - item2 165 | - item3 166 | ... 167 | v} *) 168 | val enumerate : 'a list -> f:('a -> 'tag t) -> 'tag t 169 | 170 | (** [chain l ~f] is used to print a succession of items that follow each other. 171 | It produces an output of this form: 172 | 173 | {v 174 | item1 175 | -> item2 176 | -> item3 177 | ... 178 | v} *) 179 | val chain : 'a list -> f:('a -> 'tag t) -> 'tag t 180 | 181 | (** {1 Operators} *) 182 | 183 | module O : sig 184 | (** Infix operators for [Pp.t] *) 185 | 186 | (** Same as [seq] *) 187 | val ( ++ ) : 'tag t -> 'tag t -> 'tag t 188 | end 189 | 190 | (** {1 Rendering} *) 191 | 192 | (** Render a document to a classic formatter *) 193 | val to_fmt : Format.formatter -> 'tag t -> unit 194 | 195 | val to_fmt_with_tags : 196 | Format.formatter 197 | -> 'tag t 198 | -> tag_handler:(Format.formatter -> 'tag -> 'tag t -> unit) 199 | -> unit 200 | 201 | (** {1 Ast} *) 202 | 203 | module Ast : sig 204 | (** Stable representation of [Pp.t] useful for serialization *) 205 | 206 | (** Stable abstract syntax tree for [Pp.t] that can be used for serialization 207 | and deserialization. *) 208 | type +'tag t = 209 | | Nop 210 | | Seq of 'tag t * 'tag t 211 | | Concat of 'tag t * 'tag t list 212 | | Box of int * 'tag t 213 | | Vbox of int * 'tag t 214 | | Hbox of 'tag t 215 | | Hvbox of int * 'tag t 216 | | Hovbox of int * 'tag t 217 | | Verbatim of string 218 | | Char of char 219 | | Break of (string * int * string) * (string * int * string) 220 | | Newline 221 | | Text of string 222 | | Tag of 'tag * 'tag t 223 | end 224 | 225 | (** [of_ast t] converts an [Ast.t] to a [Pp.t]. *) 226 | val of_ast : 'tag Ast.t -> 'tag t 227 | 228 | (** [to_ast t] converts a [Pp.t] to an [Ast.t]. *) 229 | val to_ast : 'tag t -> 'tag Ast.t 230 | 231 | (** {1 Comparison} *) 232 | 233 | (** [compare cmp x y] compares [x] and [y] using [cmp] to compare tags. *) 234 | val compare : ('tag -> 'tag -> int) -> 'tag t -> 'tag t -> int 235 | -------------------------------------------------------------------------------- /test/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name pp_tests) 3 | (libraries pp) 4 | (inline_tests) 5 | (preprocess 6 | (pps ppx_expect))) 7 | -------------------------------------------------------------------------------- /test/tests.ml: -------------------------------------------------------------------------------- 1 | open StdLabels 2 | open Pp.O 3 | 4 | let print pp = Format.printf "%a@." Pp.to_fmt pp 5 | let many n pp = Array.make n pp |> Array.to_list |> Pp.concat ~sep:Pp.space 6 | let xs n = many n (Pp.char 'x') 7 | let ys n = many n (Pp.char 'y') 8 | 9 | let%expect_test _ = 10 | let hello_xs n = Pp.text "Hello" ++ Pp.space ++ xs n in 11 | print (Pp.box ~indent:2 (hello_xs 200)); 12 | [%expect 13 | {| 14 | Hello x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x 15 | x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x 16 | x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x 17 | x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x 18 | x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x 19 | x x x x x x x x x x x x 20 | |}]; 21 | print (Pp.hbox (hello_xs 50)); 22 | [%expect 23 | {| 24 | Hello x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x 25 | |}]; 26 | print (Pp.vbox ~indent:2 (hello_xs 5)); 27 | [%expect {| 28 | Hello 29 | x 30 | x 31 | x 32 | x 33 | x 34 | |}]; 35 | print (Pp.hvbox ~indent:2 (hello_xs 5)); 36 | [%expect {| 37 | Hello x x x x x 38 | |}]; 39 | print (Pp.hvbox ~indent:2 (hello_xs 50)); 40 | [%expect 41 | {| 42 | Hello 43 | x 44 | x 45 | x 46 | x 47 | x 48 | x 49 | x 50 | x 51 | x 52 | x 53 | x 54 | x 55 | x 56 | x 57 | x 58 | x 59 | x 60 | x 61 | x 62 | x 63 | x 64 | x 65 | x 66 | x 67 | x 68 | x 69 | x 70 | x 71 | x 72 | x 73 | x 74 | x 75 | x 76 | x 77 | x 78 | x 79 | x 80 | x 81 | x 82 | x 83 | x 84 | x 85 | x 86 | x 87 | x 88 | x 89 | x 90 | x 91 | x 92 | x 93 | |}]; 94 | print (Pp.hovbox ~indent:2 (hello_xs 200)); 95 | [%expect 96 | {| 97 | Hello x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x 98 | x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x 99 | x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x 100 | x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x 101 | x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x 102 | x x x x x x x x x x x x 103 | |}] 104 | 105 | let%expect_test "verbatimf" = 106 | print (Pp.verbatimf "ident%d" 42); 107 | [%expect {| ident42 |}] 108 | 109 | (* Difference between box and hovbox *) 110 | let%expect_test _ = 111 | let pp f = f (xs 50 ++ Pp.break ~nspaces:2 ~shift:(-1) ++ xs 10) in 112 | print (pp (Pp.box ~indent:2)); 113 | [%expect 114 | {| 115 | x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x 116 | x x x x x x x x x x x 117 | x x x x x x x x x x 118 | |}]; 119 | print (pp (Pp.hovbox ~indent:2)); 120 | [%expect 121 | {| 122 | x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x 123 | x x x x x x x x x x x x x x x x x x x x x 124 | |}] 125 | 126 | let enum_x_and_y = Pp.enumerate [ xs; ys ] ~f:(fun f -> f 50) 127 | 128 | let%expect_test _ = 129 | print enum_x_and_y; 130 | [%expect 131 | {| 132 | - x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x 133 | x x x x x x x x x x x x 134 | - y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y 135 | y y y y y y y y y y y y 136 | |}] 137 | 138 | let%expect_test _ = 139 | print 140 | (Pp.enumerate 141 | [ Pp.enumerate [ "abc"; "def" ] ~f:Pp.text; enum_x_and_y ] 142 | ~f:(fun x -> x)); 143 | [%expect 144 | {| 145 | - - abc 146 | - def 147 | - - x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x 148 | x x x x x x x x x x x x x 149 | - y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y 150 | y y y y y y y y y y y y y 151 | |}] 152 | 153 | let%expect_test _ = 154 | print (Pp.verbatim "....." ++ Pp.box ~indent:2 (xs 50)); 155 | [%expect 156 | {| 157 | .....x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x 158 | x x x x x x x x x x x x x x |}] 159 | 160 | let error_example_1 = 161 | Pp.vbox 162 | (Pp.box (Pp.text "Error: something went wrong!") 163 | ++ Pp.cut 164 | ++ Pp.box (Pp.text "Here are a few things you can do:") 165 | ++ Pp.cut 166 | ++ Pp.enumerate 167 | ~f:(fun x -> x) 168 | [ Pp.text 169 | "read the documentation, double check the way you are using this \ 170 | software to make sure you are not doing something wrong, and \ 171 | hopefully fix the problem on your side and move on" 172 | ; Pp.text 173 | "strace furiously the program to try and understand why exactly \ 174 | it is trying to do what it is doing" 175 | ; Pp.text "report an issue upstream" 176 | ; Pp.text "if all else fails" 177 | ++ Pp.cut 178 | ++ Pp.enumerate ~f:Pp.text 179 | [ "scream loudly at your computer" 180 | ; "take a break from your keyboard" 181 | ; "clear your head and try again" 182 | ] 183 | ]) 184 | 185 | let%expect_test _ = 186 | print error_example_1; 187 | [%expect 188 | {| 189 | Error: something went wrong! 190 | Here are a few things you can do: 191 | - read the documentation, double check the way you are using this software to 192 | make sure you are not doing something wrong, and hopefully fix the problem 193 | on your side and move on 194 | - strace furiously the program to try and understand why exactly it is trying 195 | to do what it is doing 196 | - report an issue upstream 197 | - if all else fails 198 | - scream loudly at your computer 199 | - take a break from your keyboard 200 | - clear your head and try again |}] 201 | 202 | (* Wrap the formatted lines *) 203 | let%expect_test _ = 204 | print 205 | (Pp.hovbox ~indent:2 206 | (Array.make 50 (Pp.char 'x') 207 | |> Array.to_list 208 | |> Pp.concat 209 | ~sep:(Pp.custom_break ~fits:("", 2, "") ~breaks:(" \\", -1, "")))); 210 | [%expect 211 | {| 212 | x x x x x x x x x x x x x x x x x x x x x x x x x \ 213 | x x x x x x x x x x x x x x x x x x x x x x x x x 214 | |}] 215 | 216 | let%expect_test "comparison" = 217 | let x = error_example_1 218 | and y = Pp.hovbox ~indent:2 (xs 200) in 219 | let print x = Printf.printf "comparison result: %d\n" x in 220 | print (Pp.compare (fun _ _ -> 0) x y); 221 | print (Pp.compare (fun _ _ -> 0) x x); 222 | print (Pp.compare (fun _ _ -> 0) y x); 223 | [%expect 224 | {| 225 | comparison result: -1 226 | comparison result: 0 227 | comparison result: 1 |}] 228 | 229 | (* The differnces between [Pp.paragraph], [Pp.text], [Pp.verbatim] and box + 230 | [Pp.text] when inside a [Pp.vbox]. *) 231 | let%expect_test "paragraph" = 232 | let lorem = 233 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum \ 234 | euismod, nisl eget aliquam ultricies." 235 | in 236 | let seperator text _ = 237 | Pp.vbox 238 | @@ Pp.seq Pp.space 239 | (Pp.hbox 240 | @@ Pp.textf "-< %s >-%s" text 241 | (String.init (20 - String.length text) ~f:(fun _ -> '-'))) 242 | in 243 | print @@ Pp.vbox 244 | @@ Pp.concat_map ~sep:Pp.space 245 | ~f:(fun f -> f lorem) 246 | [ seperator "verbatim" 247 | ; Pp.verbatim 248 | ; seperator "text" 249 | ; Pp.text 250 | ; seperator "paragraph" 251 | ; Pp.paragraph 252 | ; seperator "hovbox + text" 253 | ; (fun x -> Pp.hovbox (Pp.text x)) 254 | ; seperator "hvbox + text" 255 | ; (fun x -> Pp.hvbox (Pp.text x)) 256 | ; seperator "hbox + text" 257 | ; (fun x -> Pp.hbox (Pp.text x)) 258 | ; seperator "vbox + text" 259 | ; (fun x -> Pp.vbox (Pp.text x)) 260 | ; seperator "box + text" 261 | ; (fun x -> Pp.box (Pp.text x)) 262 | ]; 263 | [%expect 264 | {| 265 | -< verbatim >------------- 266 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum euismod, nisl eget aliquam ultricies. 267 | 268 | -< text >----------------- 269 | Lorem 270 | ipsum 271 | dolor 272 | sit 273 | amet, 274 | consectetur 275 | adipiscing 276 | elit. 277 | Vestibulum 278 | euismod, 279 | nisl 280 | eget 281 | aliquam 282 | ultricies. 283 | 284 | -< paragraph >------------ 285 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum euismod, 286 | nisl eget aliquam ultricies. 287 | 288 | -< hovbox + text >-------- 289 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum euismod, 290 | nisl eget aliquam ultricies. 291 | 292 | -< hvbox + text >--------- 293 | Lorem 294 | ipsum 295 | dolor 296 | sit 297 | amet, 298 | consectetur 299 | adipiscing 300 | elit. 301 | Vestibulum 302 | euismod, 303 | nisl 304 | eget 305 | aliquam 306 | ultricies. 307 | 308 | -< hbox + text >---------- 309 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum euismod, nisl eget aliquam ultricies. 310 | 311 | -< vbox + text >---------- 312 | Lorem 313 | ipsum 314 | dolor 315 | sit 316 | amet, 317 | consectetur 318 | adipiscing 319 | elit. 320 | Vestibulum 321 | euismod, 322 | nisl 323 | eget 324 | aliquam 325 | ultricies. 326 | 327 | -< box + text >----------- 328 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum euismod, 329 | nisl eget aliquam ultricies. |}] 330 | 331 | let%expect_test "paragraphf" = 332 | print (Pp.paragraphf "Hello World%s" "!"); 333 | [%expect {| Hello World! |}] 334 | --------------------------------------------------------------------------------